-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Minimize the calls in useSelect by subscribing to only the stores needed #26724
Conversation
Size Change: +617 B (0%) Total Size: 1.2 MB
ℹ️ View Unchanged
|
@kevin940726 it doesn't account for selectors like export const getWidgets = createRegistrySelector( ( select ) => () => {
const widgets = select( 'core' ).getEntityRecords(
'root',
'widget',
buildWidgetsQuery()
);
return keyBy( widgets, 'id' );
} ); To make it work, you'd need to somehow hook into the actual |
@kevin940726 I played with it more and came up with this: #26733 - still not working properly though |
6866559
to
048c33b
Compare
Does it support this as well #26724 (comment) ? |
@youknowriad Yep, it's also in the unit tests :). |
0b6b5d2
to
5c099ce
Compare
@kevin940726 I think this PR is obsolete now that #26866 was merged |
Thx for the notice! Note to myself that I'll try to run some of the unit tests introduced here after rebasing and close this 👍. |
Actually, I'm considering reverting the |
5c099ce
to
0ce8271
Compare
@youknowriad Thx for the inputs! I've rebased the branch and updated the performance report in the description. |
It seems the performance impact here is good but not as good as what we had in the atom's approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the performance gains, and the code looks good as well. 🚢
Our stores are large so it's normal to have selectors "watching" a branch that remains the same over multiple "notifyListeners" calls. That's another difference with the atoms approach where only relevant changes bubble up to their observers. |
Description
Minimize the calls of the selectors in
useSelect
by only subscribing to the stores needed.The problem
Currently,
useSelect
works by subscribing to theregistry
instance and calls themapSelect
(the first argument ofuseSelect
) function every time the store changes. It works fine, but it also fires many unnecessarymapSelect
calls when we know that the store updates won't cause a re-render.Most components subscribe to the default
registry
, and most stores also registered to that sameregistry
. Let's say that some component has acounter
store and multiple other stores likecore
,core/editor
,core/block-editor
, etc. Thecounter
store has a selectorgetCounter
, which simply gets thecount
state from the store. A component using this selector could look like this.It's clear that this component doesn't care about state updates from stores other than
counter
. However, the selector still gets called when we dispatch any actions no matter if the action doesn't belong to thecounter
store.Fortunately, since the
count
state fromgetCounter()
doesn't change,<TestComponent>
doesn't have to re-render. Still, it's an unnecessary call of thegetCounter
selector. In this specific case, it doesn't matter much, but it would matter if themapSelect
function is very complex or slow. Especially when we haveresolver
for eachselector
, which could potentially cause unwanted side-effects to be introduced.The solution
This PR propose a solution to minimize the calls of the
mapSelect
function as much as possible. The idea is to only call themapSelect
function if and only if the store thismapSelect
has referenced to gets updated.For instance,
this
useSelect
only cares about updates within thecounter
store itself and nothing else. We only have to re-run themapSelect
function whenever the state from thecounter
store updates.Another example,
this
useSelect
cares about state updates from two different stores:counter
andcore/editor
. Whenever either one of the store updates, we need to re-run themapSelect
function.This is done by trapping the
select
function of theregistry
to keep track of every store it has called. It works very similarly like monkey-patching theselect
function as follow (the actual implementation is slightly different because of some issues with legacy features withuse
andwithPlugins
):Whenever
reigstry.select( storeKey )
is called, we'll push thestoreKey
to thelisteningStores
. Later, we can then only subscribe to those stores rather than subscribing to the wholeregistry
.How has this been tested?
Added a bunch of unit tests and also fixed some tests.
End-to-end tests should also be passing.
Performance tests
As seen in the report, this PR out-performs every test. However, the difference is not that big though, it might be just a micro-optimization in the end.
Types of changes
New feature
Checklist: