-
Notifications
You must be signed in to change notification settings - Fork 49.2k
[Fiber] Initial implementation of context #8272
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
Conversation
@@ -22,8 +22,7 @@ src/addons/transitions/__tests__/ReactTransitionGroup-test.js | |||
* should warn for duplicated keys with component stack info | |||
|
|||
src/isomorphic/classic/__tests__/ReactContextValidator-test.js | |||
* should filter out context not in contextTypes | |||
* should filter context properly in callbacks | |||
* should pass previous context to lifecycles |
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.
This is a new PR I extracted from "should filter context properly in callbacks". It covers the componentDidUpdate
with nextContext
which we punted on.
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 meant test, not PR
This looks much cleaner than my approach, awesome 😄 |
@@ -182,11 +189,14 @@ module.exports = function<T, P, I, TI, C>( | |||
} else { | |||
shouldUpdate = updateClassInstance(current, workInProgress); | |||
} | |||
const instance = workInProgress.stateNode; | |||
if (typeof instance.getChildContext === 'function') { | |||
pushContextProvider(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.
I should probably do this after rendering. Otherwise context provider would likely see its own context.
32846c1
to
3947531
Compare
Moved the implementation into scheduler to solve a bug with reusing work. |
We mistakingly forget to push the context if the parent is being reused but the child is not.
It is error-prone to push and pop context in begin or complete phases because of all the bailouts. Scheduler has enough knowledge to see if pushing is necessary because it knows when we go inside or outside the tree.
f273275
to
f9e1bc9
Compare
@@ -267,6 +274,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) { | |||
const current = workInProgress.alternate; | |||
const next = completeWork(current, workInProgress); | |||
|
|||
// We are leaving this subtree, so pop context if any. | |||
if (isContextProvider(workInProgress)) { | |||
popContextProvider(); |
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 was hoping that we would be able to put push/pop inside ReactFiberBeginWork/ReactFiberCompleteWork. The reason is that I'd like to be able to reuse the tag check inside the switch statement so that we only even check for this if we hit the ClassComponent branch. That way when we build optimized paths, they can completely skip this check.
I realize that this might be currently broken because we bail out before the switch statement in certain cases right now. We might have to move those around.
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID); | ||
} | ||
|
||
const mergedContext = Object.assign({}, getUnmaskedContext(), childContext); |
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 use spread with our Object.assign
rewrite?
e7ed64c
to
9ab904a
Compare
As per discussion, this is better because then this code only runs for the class type of work. We will still need to fix bailouts for deep setState calls.
9ab904a
to
56e1c12
Compare
|
||
let index = -1; | ||
const contextStack : Array<Object> = []; | ||
const didPerformWorkStack : Array<boolean> = []; |
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.
Could these arrays be combined into a single linked list? From what I can tell it doesn't look like you need indexing because you only ever need to access the tail.
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.
In this case it'll (probably) be more efficient to use an array since we won't need to reorder anything and we won't need to reallocate anything when it grows and shrinks.
We can avoid recreating the merged context object if the context provider work was reused. This is essential to avoid allocations for deep setState() calls.
type.name || (constructor && constructor.name) || | ||
'A Component' | ||
); | ||
}; |
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 have the getComponentName
module that you could use:
https://github.com/facebook/react/blob/master/src/shared/utils/getComponentName.js#L18-L38
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 tried but it was intentionally DEV-only at the time, and I need it for an invariant.
If we're ok increasing non-Fiber size with Fiber code before Fiber is ready, I could remove the DEV gate there.
@@ -125,6 +129,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) { | |||
return null; | |||
case ClassComponent: | |||
transferOutput(workInProgress.child, workInProgress); | |||
// We are leaving this subtree, so pop context if any. | |||
if (isContextProvider(workInProgress)) { | |||
popContextProvider(); |
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.
If we unwind to catch an error, we need to unwind the context as well so that we can continue with the next sibling after the error boundary.
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 was thinking instead of literally unwinding to catch an error, we schedule an update on the boundary and reset the unit of work pointer. That's how I've started to implement it, not totally sure if it will work yet.
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.
Where by "schedule an update" I mean give it work priority
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.
This is not relevant now though, is it?
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.
What I'm trying to say is that if we always start from the root, I don't think unwinding the context is necessary because of your line here: https://github.com/facebook/react/pull/8272/files#diff-74dedebbf08d8dcec2e92b8931274454R377
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.
Yea that could work.
This matches Stack behavior. Had to rewrite a test that used Simulate because Fiber doesn't support events yet. Also changed tests for componentDidUpdate() since Fiber intentionally doesn't pass prevContext argument.
@@ -249,13 +262,15 @@ module.exports = function<T, P, I, TI, C>( | |||
} | |||
var fn = workInProgress.type; | |||
var props = workInProgress.pendingProps; | |||
var context = getMaskedContext(workInProgress); | |||
|
|||
var value; | |||
|
|||
if (__DEV__) { |
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.
what the reason of such style?
why here you can't just have
var value = fn(props, context);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
}
or
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
}
var value = fn(props, context);
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 don't think there's any reason for this, putting just current owner assignment in a condition would be equivalent.
Known issues:
nextContext
tocomponentDidUpdate()
would require changing the approach and adding a field to Fiber. For now we decided to bail on supporting this use case (e.g. Facebook doesn't have a single component in thousands using this pattern). We'll probably make a breaking change in Stack as well for this in 16.