Repository navigation
Conversation
Every root node created a reaction on its snapshot, whether or not anyone called onSnapshot. It existed to keep the snapshot computeds observed so they stay cached, but it also made every action recompute the snapshot of each changed node and all its ancestors. The snapshot computed is now kept alive instead. It stays cached, but it is only recomputed when it is read. onSnapshot still adds the reaction. onSnapshot now creates that reaction, so inside an action it first runs when the action ends. It takes its baseline when the listener is added and fires immediately, emitting only if the snapshot changed, so a listener added in an action still gets the changes made after it. A kept-alive computed never suspends, so it is re-run: - when a snapshot processor replaces getSnapshot of an existing node (a detached node moved into a processed array); - when the node dies, so it stops observing what it read. A dead node's computed returns the snapshot taken upon death, so this does not call the post-processor on the dead node.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do and why?
After every action, each MST root rebuilds its snapshot, even when the app never calls
getSnapshotoronSnapshot. This PR rebuilds the snapshot only when something reads it.Our use case. A design is an MST tree: pages with elements, and each element has about 60 properties (position, size, rotation, fills, text settings, …). When a user drags many selected elements, every mouse move updates
xandyof all of them. We keep these positions in the model during the drag, not only on the canvas, because other code depends on them live: snapping and alignment guides compare the dragged elements with the others, constraints check the new positions, and other parts of the UI show them. So dragging 600 elements is one action per mouse move that changes 1,200 properties, and MST then rebuilds the snapshot of all 600 elements (about 36,000 properties). Nothing reads it.A pure MST version of that drag:
dist/mobx-state-tree.min.js, Node 26, median of 50 runs:getSnapshot: unchanged (~27 ms). The snapshot is now built when it is read, not before.onSnapshotlistener: unchanged. A listener still needs a new snapshot after every action.Why it happens. Since #902 (2018), each root creates
reaction(() => this.snapshot, ...)when it is created (before that, every node did). It was kept on roots so that the snapshot computed stays observed and therefore cached. But a reaction also recomputes the snapshot after every change.Changes
{ keepAlive: true }, so it stays cached without an observer, and the eager root reaction is removed.onSnapshotstill adds its reaction.onSnapshottakes its first snapshot when the listener is added (untracked,fireImmediately, skip if unchanged), so a listener added inside an action still gets the changes made after it.snapshotProcessorreplacesgetSnapshotof an existing node.Behaviour changes
onSnapshotlistener added in the middle of an action no longer gets the changes made earlier in that action. Non-root listeners already work like this.onSnapshotlistener added in an action now gets the changes made later in that action.toSnapshot) throws when it is read, not at the end of every action.