@samitouri / QOS-React / commits / cb6da8e922

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;