-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Improve unmasked context caching #8723
Improve unmasked context caching #8723
Conversation
@@ -57,6 +57,18 @@ function getUnmaskedContext(workInProgress : Fiber) : Object { | |||
} | |||
exports.getUnmaskedContext = getUnmaskedContext; | |||
|
|||
function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) { | |||
if (isContextConsumer(workInProgress)) { |
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.
We already doing an equivalent check in getMaskedContext
which is called just before cacheContext
.
Can we avoid doing it twice?
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.
The thing that makes this tricky is that instance
won't always exist yet when getMaskedContext
is called. That's the whole reason the separate function exists at all.
What would you suggest?
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.
Right, but we don’t need the instance
to tell if it’s a context consumer or not.
Or do we?
I thought that maybe we can make getMaskedContext
return null
for non-context consumers. Then we’d know for sure that we don’t need to call cacheContext
.
Not saying it’s the best way, just a proposal.
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.
You are correct. We do not need instance
for that. I just mentioned that we need to do some kind of duplicate work since instance
won't always exist.
getMaskedContext
returns emptyObject
for non-consumers. We could compare it? eg
function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) {
if (maskedContext !== emptyObject) {
const instance = workInProgress.stateNode;
if (instance) {
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
}
}
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.
We could but it seems a bit fishy to make one perf optimization to rely on another perf optimization being present in another file. Returning null
since more explicit if we want to "signal" that it's not a context consumer.
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.
Alternatively we could call isContextConsumer
from outside in the first place.
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 assume that getMaskedContext
returns emptyObject
instead of null
for a reason.
We could but it seems a bit fishy to make one perf optimization to rely on another perf optimization being present in another file.
This all inside of ReactFiberContext
. What's the other file?
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.
Ah! I think I understand.
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 assume that getMaskedContext returns emptyObject instead of null for a reason.
Because we want to pass an empty object to components. But we could do || emptyObject
this here (and similar places) instead.
This all inside of ReactFiberContext. What's the other file?
I am talking about these lines in ClassComponent
specifically:
// This will check if it's a context consumer:
const context = getMaskedContext(workInProgress, unmaskedContext);
// ...
// This will do the same check:
cacheContext(workInProgress, unmaskedContext, context);
I was proposing to do something like
// Check once:
const needsContext = isContextConsumer(workInProgress);
const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject;
// ...
if (needsContext) {
cacheContext(workInProgress, unmaskedContext, context);
}
Then you could remove checks in both getMaskedContext
an cacheContext
.
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.
Yup. Thanks for clarifying. 😄 Updated.
a06bb12
to
d9f3528
Compare
@@ -57,6 +57,15 @@ function getUnmaskedContext(workInProgress : Fiber) : Object { | |||
} | |||
exports.getUnmaskedContext = getUnmaskedContext; | |||
|
|||
function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) { | |||
const instance = workInProgress.stateNode; | |||
if (instance) { |
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.
Can we move this check in getMaskedContext
instead?
The other caller of cacheContext
always has the instance.
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.
Sure, we could do that. I didn't do it initially because it seemed unsafe. Fiber
may not have an instance
and I didn't wan to rely on external callers of this function having to know only to call this method when an instance
existed.
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.
PS. I'm happy to defer to your judgement here. You're more familiar with the project and the way things are done. Just sharing my thought process. 😄
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.
Generally I'm of the opinion that methods should be strict and either have a safety check inside (and we never do it outside) or have no safety check, and we must always do it outside.
Doing it both ways means somebody will see the "early check" pattern that was made for performance reasons and scratch their head why other calls aren't behind the same check. This will look "surprising" if one call is different from other calls in the same file. And every difference like this is mental overhead when you have to move those pieces around. Strict contracts help avoid that, and we can always fix safety concerns with tests for regressions.
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.
Okay. I understand your thoughts now. Thank you for elaborating.
} | ||
} | ||
exports.cacheContext = cacheContext; | ||
|
||
exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Object) { | ||
const type = workInProgress.type; | ||
const contextTypes = type.contextTypes; |
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.
Can we remove this check here now, and always do it externally?
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.
We could. Same safety-related concern as above.
Edit: I think I'm going to leave this as-is since we call getMaskedContext
in half a dozen places and it seems to easy to mess up if we require all callers to check isContextConsumer
before calling.
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.
👍
Looks good to me. I don't have a strong preference for the above comments, please do as it makes more sense to you. |
Only store cached masked/unmasked contexts on context consumers. Move all references to __reactInternal* cached attributes inside of ReactFiberContext.
d9f3528
to
e5a7b75
Compare
context
consumers.ReactFiberContext
.Related to this discussion thread.