Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Async mode in the data module #13056
This is a crazy experiment :)
The idea is to build an async mode into the data module. Allowing some part of the React Tree to declare them selves as "async" which basically means low priority.
If a component is in async mode, it won't react synchronously to store changes, instead it will wait for all the browser's high-priority work (typing, rendering...)
In its current state, this PR declares the whole block list (the content of the post) as low priority, which means this part won't rerender synchronously when you type for instance but the chrome (save buttons, sidebar...) will rerender in sync.
I think this may introduce some small bugs we need to track and see how to fix (slot/fill maybe others).
That said, the feeling of responsiveness is highly improved with this PR. For instance you can try with this post https://gist.github.com/florianbrinkmann/4e9e4d23eaefb8b23484badddd848196 (convert it to blocks first) and compare the feeling with master.
I think this proposal makes a lot of sense. In particular, the way this async mode is introduced seems to be reasonable (as far as I understood these changes). I read it as this async mode is applied to all unselected blocks when there is no multi-selection mode enabled.
I guess that the failing
This makes me think, whether we should also explore the opposite approach at some point, make everything async by default and mark as sync only those branches of virtual DOM as sync where it is essential to provide a compelling UX.
2 times, most recently
Jan 7, 2019
I'm still figuring out how best to measure things here, but I'm seeing a definite improvement in terms of responsiveness. In the old code, there's a big monolithic set of changes that causes a huge 2.4 second frame, in my test case (typing in a large document with a 6x CPU throttling multiplier):
In the new code, the work doesn't take as long (1.7s), but more importantly it's split up into multiple frames, which allow for more visual feedback and thus higher responsiveness:
This evidence is mostly anecdotal, as I'd still like to find a good way of running repeated tests and getting some more accurate numbers out. Not sure how I'll do that yet, but it'll likely involve Puppeteer :)
When reading the patch, this is how I understand it: when the store changes and listeners are fired, the listeners registered by
That's almost exactly what
@jsnajdr Actually, the principles are the same but there's a small and subtle difference here that makes a big difference comparing to React Async Mode and won't be solved by it.
The main performance problem while the number of components grow in a React/Redux application is not the rerendering of all the components as most likely these components already have logic (pure and similar) to avoid rerendering them if the props/state don't change. That's exactly what we already have in Gutenberg. So if I type in a block, there's only the current block that rerenders with some chrome components but the other blocks don't.
The main issue comes from the fact that we're obliged to run the selectors (even if memoized and fast) for all the components and all the blocks to determine the next props and whether we should rerender or not. And the selectors calls happens before
This feels like a good solution, given the constraints, and I agree with @youknowriad that this solves issues at a different layer than what React's async mode does. I completely agree with the assertion that the issue in Gutenberg is not that components update too often, but rather that too many selectors are running too often, and that can only be solved at the data layer.
It's unfortunate, though. The more I learn about Redux (and particularly the way it interacts with React), the narrower the scope of use-cases that it seems to be an optimum solution for feels.
Gutenberg has already needed a number of non-trivial architectural workarounds, beyond the expected selector performance tweaking: multiple stores; a custom implementation of the React bindings that only runs selectors when the state tree mutates (
And this is by no means a "write and forget" thing. It will take constant vigilance to make sure all these pieces keep functioning optimally :-/
@youknowriad Thanks for the clarification! I was wondering whether the performance sensitive part is running of the selectors, or the actual rerender. React will help only with the latter of course
Do I understand correctly that the main goal of this change is to make typing more responsive?
Doing a little profiling (in the Calypso version) I see a lot of things is happening on every keystroke. The
The Calypso Classic editor, the one that uses TinyMCE, isolates the content change on typing inside the TinyMCE component and synchronizes the content to Redux only later, when the user stops typing. Using
Would that be an option for typing inside a block, too? Dispatching an