Fiber: renderSubtreeIntoContainer (#8368)
Ben Alpert committed
Nov 22, 2016 at 09:57 UTC
cb6da8e922c426dfae11a5da159ff9e8e176ce59
14 files changed
+172
-54
scripts/fiber/tests-failing.txt
-5
@@ -6,11 +6,6 @@ src/addons/__tests__/ReactFragment-test.js
6
* should throw if a plain object even if it is in an owner
7
* should throw if a plain object looks like an old element
8
9
-src/addons/__tests__/renderSubtreeIntoContainer-test.js
10
-* should pass context when rendering subtree elsewhere
11
-* should update context if it changes due to setState
12
-* should update context if it changes due to re-render
13
-
9
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
10
* should pass previous context to lifecycles
11
scripts/fiber/tests-passing.txt
+3
@@ -44,7 +44,10 @@ src/addons/__tests__/ReactFragment-test.js
44
* should warn if passing a ReactElement to createFragment
45
46
src/addons/__tests__/renderSubtreeIntoContainer-test.js
47
+* should pass context when rendering subtree elsewhere
48
* should throw if parentComponent is invalid
49
+* should update context if it changes due to setState
50
+* should update context if it changes due to re-render
51
52
src/addons/__tests__/update-test.js
53
* pushes
src/renderers/dom/ReactDOM.js
+1
-2
@@ -23,7 +23,6 @@ var ReactVersion = require('ReactVersion');
23
24
var findDOMNode = require('findDOMNode');
25
var getHostComponentFromComposite = require('getHostComponentFromComposite');
26
-var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
26
var warning = require('warning');
27
28
ReactDOMInjection.inject();
@@ -37,7 +36,7 @@ var ReactDOM = {
36
37
/* eslint-disable camelcase */
38
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
40
- unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer,
39
+ unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer,
40
/* eslint-enable camelcase */
41
};
42
src/renderers/dom/fiber/ReactDOMFiber.js
+26
-8
@@ -16,15 +16,22 @@ import type { Fiber } from 'ReactFiber';
16
import type { HostChildren } from 'ReactFiberReconciler';
17
18
var ReactControlledComponent = require('ReactControlledComponent');
19
-var ReactFiberReconciler = require('ReactFiberReconciler');
19
var ReactDOMComponentTree = require('ReactDOMComponentTree');
20
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
21
var ReactDOMFiberComponent = require('ReactDOMFiberComponent');
22
var ReactDOMInjection = require('ReactDOMInjection');
23
+var ReactFiberReconciler = require('ReactFiberReconciler');
24
+var ReactInstanceMap = require('ReactInstanceMap');
25
26
var findDOMNode = require('findDOMNode');
27
+var invariant = require('invariant');
28
var warning = require('warning');
29
30
+ReactDOMInjection.inject();
31
+ReactControlledComponent.injection.injectFiberControlledHostComponent(
32
+ ReactDOMFiberComponent
33
+);
34
+
35
var {
36
createElement,
37
setInitialProperties,
@@ -147,18 +154,29 @@ function warnAboutUnstableUse() {
154
warned = true;
155
}
156
157
+function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
158
+ let root;
159
+ if (!container._reactRootContainer) {
160
+ root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
161
+ } else {
162
+ DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
163
+ }
164
+ return DOMRenderer.getPublicRootInstance(root);
165
+}
166
+
167
var ReactDOM = {
168
169
render(element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
170
warnAboutUnstableUse();
154
- let root;
171
+ return renderSubtreeIntoContainer(null, element, container, callback);
172
+ },
173
156
- if (!container._reactRootContainer) {
157
- root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, callback);
158
- } else {
159
- DOMRenderer.updateContainer(element, root = container._reactRootContainer, callback);
160
- }
161
- return DOMRenderer.getPublicRootInstance(root);
174
+ unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
175
+ invariant(
176
+ parentComponent != null && ReactInstanceMap.has(parentComponent),
177
+ 'parentComponent must be a valid React Component'
178
+ );
179
+ return renderSubtreeIntoContainer(parentComponent, element, container, callback);
180
},
181
182
unmountComponentAtNode(container : DOMContainerElement) {
src/renderers/dom/stack/client/ReactMount.js
+2
-9
@@ -27,7 +27,7 @@ var ReactReconciler = require('ReactReconciler');
27
var ReactUpdateQueue = require('ReactUpdateQueue');
28
var ReactUpdates = require('ReactUpdates');
29
30
-var emptyObject = require('emptyObject');
30
+var getContextForSubtree = require('getContextForSubtree');
31
var instantiateReactComponent = require('instantiateReactComponent');
32
var invariant = require('invariant');
33
var setInnerHTML = require('setInnerHTML');
@@ -466,14 +466,7 @@ var ReactMount = {
466
{ child: nextElement }
467
);
468
469
- var nextContext;
470
- if (parentComponent) {
471
- var parentInst = ReactInstanceMap.get(parentComponent);
472
- nextContext = parentInst._processChildContext(parentInst._context);
473
- } else {
474
- nextContext = emptyObject;
475
- }
476
-
469
+ var nextContext = getContextForSubtree(parentComponent);
470
var prevComponent = getTopLevelWrapperInContainer(container);
471
472
if (prevComponent) {
src/renderers/dom/stack/client/renderSubtreeIntoContainer.js
+2
-2
@@ -11,6 +11,6 @@
11
12
'use strict';
13
14
-var ReactMount = require('ReactMount');
14
+var ReactDOM = require('ReactDOM');
15
16
-module.exports = ReactMount.renderSubtreeIntoContainer;
16
+module.exports = ReactDOM.unstable_renderSubtreeIntoContainer;
src/renderers/noop/ReactNoop.js
+2
-2
@@ -182,12 +182,12 @@ var ReactNoop = {
182
if (!roots.has(rootID)) {
183
const container = { rootID: rootID, children: [] };
184
rootContainers.set(rootID, container);
185
- const root = NoopRenderer.mountContainer(element, container, callback);
185
+ const root = NoopRenderer.mountContainer(element, container, null, callback);
186
roots.set(rootID, root);
187
} else {
188
const root = roots.get(rootID);
189
if (root) {
190
- NoopRenderer.updateContainer(element, root, callback);
190
+ NoopRenderer.updateContainer(element, root, null, callback);
191
}
192
}
193
},
src/renderers/shared/fiber/ReactFiberBeginWork.js
+13
-1
@@ -14,6 +14,7 @@
14
15
import type { ReactCoroutine } from 'ReactCoroutine';
16
import type { Fiber } from 'ReactFiber';
17
+import type { FiberRoot } from 'ReactFiberRoot';
18
import type { HostConfig } from 'ReactFiberReconciler';
19
import type { PriorityLevel } from 'ReactPriorityLevel';
20
@@ -30,6 +31,7 @@ var {
31
isContextProvider,
32
hasContextChanged,
33
pushContextProvider,
34
+ pushTopLevelContextObject,
35
resetContext,
36
} = require('ReactFiberContext');
37
var {
@@ -411,11 +413,21 @@ module.exports = function<T, P, I, TI, C>(
413
return updateFunctionalComponent(current, workInProgress);
414
case ClassComponent:
415
return updateClassComponent(current, workInProgress);
414
- case HostContainer:
416
+ case HostContainer: {
417
+ const root = (workInProgress.stateNode : FiberRoot);
418
+ if (root.pendingContext) {
419
+ pushTopLevelContextObject(
420
+ root.pendingContext,
421
+ root.pendingContext !== root.context
422
+ );
423
+ } else {
424
+ pushTopLevelContextObject(root.context, false);
425
+ }
426
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
427
// A yield component is just a placeholder, we can just run through the
428
// next one immediately.
429
return workInProgress.child;
430
+ }
431
case HostComponent:
432
if (workInProgress.stateNode && typeof config.beginUpdate === 'function') {
433
config.beginUpdate(workInProgress.stateNode);
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+9
-1
@@ -14,6 +14,7 @@
14
15
import type { ReactCoroutine } from 'ReactCoroutine';
16
import type { Fiber } from 'ReactFiber';
17
+import type { FiberRoot } from 'ReactFiberRoot';
18
import type { HostConfig } from 'ReactFiberReconciler';
19
import type { ReifiedYield } from 'ReactReifiedYield';
20
@@ -157,8 +158,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
158
markCallback(workInProgress);
159
}
160
return null;
160
- case HostContainer:
161
+ case HostContainer: {
162
transferOutput(workInProgress.child, workInProgress);
163
+ popContextProvider();
164
+ const fiberRoot = (workInProgress.stateNode : FiberRoot);
165
+ if (fiberRoot.pendingContext) {
166
+ fiberRoot.context = fiberRoot.pendingContext;
167
+ fiberRoot.pendingContext = null;
168
+ }
169
// We don't know if a container has updated any children so we always
170
// need to update it right now. We schedule this side-effect before
171
// all the other side-effects in the subtree. We need to schedule it
@@ -166,6 +173,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
173
// are invoked.
174
markUpdate(workInProgress);
175
return null;
176
+ }
177
case HostComponent:
178
let newProps = workInProgress.pendingProps;
179
if (current && workInProgress.stateNode != null) {
src/renderers/shared/fiber/ReactFiberContext.js
+49
-18
@@ -18,6 +18,7 @@ var emptyObject = require('emptyObject');
18
var invariant = require('invariant');
19
var {
20
getComponentName,
21
+ isFiberMounted,
22
} = require('ReactFiberTreeReflection');
23
var {
24
ClassComponent,
@@ -64,12 +65,13 @@ exports.hasContextChanged = function() : boolean {
65
return index > -1 && didPerformWorkStack[index];
66
};
67
67
-exports.isContextProvider = function(fiber : Fiber) : boolean {
68
+function isContextProvider(fiber : Fiber) : boolean {
69
return (
70
fiber.tag === ClassComponent &&
71
typeof fiber.stateNode.getChildContext === 'function'
72
);
72
-};
73
+}
74
+exports.isContextProvider = isContextProvider;
75
76
exports.popContextProvider = function() : void {
77
contextStack[index] = emptyObject;
@@ -77,10 +79,36 @@ exports.popContextProvider = function() : void {
79
index--;
80
};
81
80
-exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
82
+exports.pushTopLevelContextObject = function(context : Object, didChange : boolean) : void {
83
+ invariant(index === -1, 'Unexpected context found on stack');
84
+ index++;
85
+ contextStack[index] = context;
86
+ didPerformWorkStack[index] = didChange;
87
+};
88
+
89
+function processChildContext(fiber : Fiber, parentContext : Object): Object {
90
const instance = fiber.stateNode;
91
const childContextTypes = fiber.type.childContextTypes;
92
+ const childContext = instance.getChildContext();
93
+ for (let contextKey in childContext) {
94
+ invariant(
95
+ contextKey in childContextTypes,
96
+ '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
97
+ getComponentName(fiber),
98
+ contextKey
99
+ );
100
+ }
101
+ if (__DEV__) {
102
+ const name = getComponentName(fiber);
103
+ const debugID = 0; // TODO: pass a real ID
104
+ checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
105
+ }
106
+ return {...parentContext, ...childContext};
107
+}
108
+exports.processChildContext = processChildContext;
109
110
+exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
111
+ const instance = fiber.stateNode;
112
const memoizedMergedChildContext = instance.__reactInternalMemoizedMergedChildContext;
113
const canReuseMergedChildContext = !didPerformWork && memoizedMergedChildContext != null;
114
@@ -88,21 +116,7 @@ exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean)
116
if (canReuseMergedChildContext) {
117
mergedContext = memoizedMergedChildContext;
118
} else {
91
- const childContext = instance.getChildContext();
92
- for (let contextKey in childContext) {
93
- invariant(
94
- contextKey in childContextTypes,
95
- '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
96
- getComponentName(fiber),
97
- contextKey
98
- );
99
- }
100
- if (__DEV__) {
101
- const name = getComponentName(fiber);
102
- const debugID = 0; // TODO: pass a real ID
103
- checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
104
- }
105
- mergedContext = {...getUnmaskedContext(), ...childContext};
119
+ mergedContext = processChildContext(fiber, getUnmaskedContext());
120
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
121
}
122
@@ -115,3 +129,20 @@ exports.resetContext = function() : void {
129
index = -1;
130
};
131
132
+exports.findCurrentUnmaskedContext = function(fiber: Fiber) : Object {
133
+ // Currently this is only used with renderSubtreeIntoContainer; not sure if it
134
+ // makes sense elsewhere
135
+ invariant(
136
+ isFiberMounted(fiber) && fiber.tag === ClassComponent,
137
+ 'Expected subtree parent to be a mounted class component'
138
+ );
139
+
140
+ let node : ?Fiber = parent;
141
+ while (node) {
142
+ if (isContextProvider(fiber)) {
143
+ return fiber.stateNode.__reactInternalMemoizedMergedChildContext;
144
+ }
145
+ node = node.return;
146
+ }
147
+ return emptyObject;
148
+};
src/renderers/shared/fiber/ReactFiberReconciler.js
+17
-5
@@ -17,6 +17,10 @@ import type { FiberRoot } from 'ReactFiberRoot';
17
import type { TypeOfWork } from 'ReactTypeOfWork';
18
import type { PriorityLevel } from 'ReactPriorityLevel';
19
20
+var {
21
+ findCurrentUnmaskedContext,
22
+ processChildContext,
23
+} = require('ReactFiberContext');
24
var { createFiberRoot } = require('ReactFiberRoot');
25
var ReactFiberScheduler = require('ReactFiberScheduler');
26
@@ -28,6 +32,8 @@ if (__DEV__) {
32
33
var { findCurrentHostFiber } = require('ReactFiberTreeReflection');
34
35
+var getContextForSubtree = require('getContextForSubtree');
36
+
37
export type Deadline = {
38
timeRemaining : () => number
39
};
@@ -64,8 +70,8 @@ export type HostConfig<T, P, I, TI, C> = {
70
};
71
72
export type Reconciler<C, I, TI> = {
67
- mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
68
- updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
73
+ mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
74
+ updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
75
unmountContainer(container : OpaqueNode) : void,
76
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
77
/* eslint-disable no-undef */
@@ -81,6 +87,10 @@ export type Reconciler<C, I, TI> = {
87
findHostInstance(component : Fiber) : I | TI | null,
88
};
89
90
+getContextForSubtree._injectFiber(function(fiber : Fiber) {
91
+ return processChildContext(fiber, findCurrentUnmaskedContext(fiber));
92
+});
93
+
94
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I, TI> {
95
96
var {
@@ -92,8 +102,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
102
103
return {
104
95
- mountContainer(element : ReactElement<any>, containerInfo : C, callback: ?Function) : OpaqueNode {
96
- const root = createFiberRoot(containerInfo);
105
+ mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
106
+ const context = getContextForSubtree(parentComponent);
107
+ const root = createFiberRoot(containerInfo, context);
108
const container = root.current;
109
if (callback) {
110
const queue = createUpdateQueue(null);
@@ -117,7 +128,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
128
return container;
129
},
130
120
- updateContainer(element : ReactElement<any>, container : OpaqueNode, callback: ?Function) : void {
131
+ updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
132
// TODO: If this is a nested container, this won't be the root.
133
const root : FiberRoot = (container.stateNode : any);
134
if (callback) {
@@ -127,6 +138,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
138
addCallbackToQueue(queue, callback);
139
root.callbackList = queue;
140
}
141
+ root.pendingContext = getContextForSubtree(parentComponent);
142
// TODO: Use pending work/state instead of props.
143
root.current.pendingProps = element;
144
src/renderers/shared/fiber/ReactFiberRoot.js
+6
-1
@@ -28,9 +28,12 @@ export type FiberRoot = {
28
nextScheduledRoot: ?FiberRoot,
29
// Linked list of callbacks to call after updates are committed.
30
callbackList: ?UpdateQueue,
31
+ // Top context object, used by renderSubtreeIntoContainer
32
+ context: Object,
33
+ pendingContext: ?Object,
34
};
35
33
-exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
36
+exports.createFiberRoot = function(containerInfo : any, context : Object) : FiberRoot {
37
// Cyclic construction. This cheats the type system right now because
38
// stateNode is any.
39
const uninitializedFiber = createHostContainerFiber();
@@ -40,6 +43,8 @@ exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
43
isScheduled: false,
44
nextScheduledRoot: null,
45
callbackList: null,
46
+ context: context,
47
+ pendingContext: null,
48
};
49
uninitializedFiber.stateNode = root;
50
return root;
src/renderers/shared/fiber/ReactFiberTreeReflection.js
+1
@@ -61,6 +61,7 @@ function isFiberMounted(fiber : Fiber) : number {
61
// that has been unmounted.
62
return UNMOUNTED;
63
}
64
+exports.isFiberMounted = isFiberMounted;
65
66
exports.isMounted = function(component : ReactComponent<any, any, any>) : boolean {
67
var fiber : ?Fiber = ReactInstanceMap.get(component);
src/renderers/shared/shared/getContextForSubtree.js
new
+41
@@ -0,0 +1,41 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule getContextForSubtree
10
+ * @flow
11
+ */
12
+
13
+import type { Fiber } from 'ReactFiber';
14
+
15
+const ReactInstanceMap = require('ReactInstanceMap');
16
+
17
+const emptyObject = require('emptyObject');
18
+const invariant = require('invariant');
19
+
20
+let getContextFiber = function(arg) {
21
+ invariant(false, 'Missing injection for fiber getContextForSubtree');
22
+};
23
+
24
+function getContextForSubtree(parentComponent : ?ReactComponent<any, any, any>) : Object {
25
+ if (!parentComponent) {
26
+ return emptyObject;
27
+ }
28
+
29
+ const instance = ReactInstanceMap.get(parentComponent);
30
+ if (typeof instance.tag === 'number') {
31
+ return getContextFiber(instance);
32
+ } else {
33
+ return instance._processChildContext(instance._context);
34
+ }
35
+}
36
+
37
+getContextForSubtree._injectFiber = function(fn) {
38
+ getContextFiber = fn;
39
+};
40
+
41
+module.exports = getContextForSubtree;