Fiber: renderSubtreeIntoContainer (#8368)

This commit is contained in:
Ben Alpert
2016-11-22 09:57:13 -08:00
committed by GitHub
parent f686218268
commit cb6da8e922
14 changed files with 172 additions and 54 deletions
-5
View File
@@ -6,11 +6,6 @@ src/addons/__tests__/ReactFragment-test.js
* should throw if a plain object even if it is in an owner
* should throw if a plain object looks like an old element
src/addons/__tests__/renderSubtreeIntoContainer-test.js
* should pass context when rendering subtree elsewhere
* should update context if it changes due to setState
* should update context if it changes due to re-render
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should pass previous context to lifecycles
+3
View File
@@ -44,7 +44,10 @@ src/addons/__tests__/ReactFragment-test.js
* should warn if passing a ReactElement to createFragment
src/addons/__tests__/renderSubtreeIntoContainer-test.js
* should pass context when rendering subtree elsewhere
* should throw if parentComponent is invalid
* should update context if it changes due to setState
* should update context if it changes due to re-render
src/addons/__tests__/update-test.js
* pushes
+1 -2
View File
@@ -23,7 +23,6 @@ var ReactVersion = require('ReactVersion');
var findDOMNode = require('findDOMNode');
var getHostComponentFromComposite = require('getHostComponentFromComposite');
var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
var warning = require('warning');
ReactDOMInjection.inject();
@@ -37,7 +36,7 @@ var ReactDOM = {
/* eslint-disable camelcase */
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer,
unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer,
/* eslint-enable camelcase */
};
+26 -8
View File
@@ -16,15 +16,22 @@ import type { Fiber } from 'ReactFiber';
import type { HostChildren } from 'ReactFiberReconciler';
var ReactControlledComponent = require('ReactControlledComponent');
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactDOMFiberComponent = require('ReactDOMFiberComponent');
var ReactDOMInjection = require('ReactDOMInjection');
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactInstanceMap = require('ReactInstanceMap');
var findDOMNode = require('findDOMNode');
var invariant = require('invariant');
var warning = require('warning');
ReactDOMInjection.inject();
ReactControlledComponent.injection.injectFiberControlledHostComponent(
ReactDOMFiberComponent
);
var {
createElement,
setInitialProperties,
@@ -147,18 +154,29 @@ function warnAboutUnstableUse() {
warned = true;
}
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
let root;
if (!container._reactRootContainer) {
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
} else {
DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
}
return DOMRenderer.getPublicRootInstance(root);
}
var ReactDOM = {
render(element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
warnAboutUnstableUse();
let root;
return renderSubtreeIntoContainer(null, element, container, callback);
},
if (!container._reactRootContainer) {
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, callback);
} else {
DOMRenderer.updateContainer(element, root = container._reactRootContainer, callback);
}
return DOMRenderer.getPublicRootInstance(root);
unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
invariant(
parentComponent != null && ReactInstanceMap.has(parentComponent),
'parentComponent must be a valid React Component'
);
return renderSubtreeIntoContainer(parentComponent, element, container, callback);
},
unmountComponentAtNode(container : DOMContainerElement) {
+2 -9
View File
@@ -27,7 +27,7 @@ var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');
var emptyObject = require('emptyObject');
var getContextForSubtree = require('getContextForSubtree');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
@@ -466,14 +466,7 @@ var ReactMount = {
{ child: nextElement }
);
var nextContext;
if (parentComponent) {
var parentInst = ReactInstanceMap.get(parentComponent);
nextContext = parentInst._processChildContext(parentInst._context);
} else {
nextContext = emptyObject;
}
var nextContext = getContextForSubtree(parentComponent);
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
@@ -11,6 +11,6 @@
'use strict';
var ReactMount = require('ReactMount');
var ReactDOM = require('ReactDOM');
module.exports = ReactMount.renderSubtreeIntoContainer;
module.exports = ReactDOM.unstable_renderSubtreeIntoContainer;
+2 -2
View File
@@ -182,12 +182,12 @@ var ReactNoop = {
if (!roots.has(rootID)) {
const container = { rootID: rootID, children: [] };
rootContainers.set(rootID, container);
const root = NoopRenderer.mountContainer(element, container, callback);
const root = NoopRenderer.mountContainer(element, container, null, callback);
roots.set(rootID, root);
} else {
const root = roots.get(rootID);
if (root) {
NoopRenderer.updateContainer(element, root, callback);
NoopRenderer.updateContainer(element, root, null, callback);
}
}
},
@@ -14,6 +14,7 @@
import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { PriorityLevel } from 'ReactPriorityLevel';
@@ -30,6 +31,7 @@ var {
isContextProvider,
hasContextChanged,
pushContextProvider,
pushTopLevelContextObject,
resetContext,
} = require('ReactFiberContext');
var {
@@ -411,11 +413,21 @@ module.exports = function<T, P, I, TI, C>(
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(current, workInProgress);
case HostContainer:
case HostContainer: {
const root = (workInProgress.stateNode : FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
root.pendingContext,
root.pendingContext !== root.context
);
} else {
pushTopLevelContextObject(root.context, false);
}
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
// A yield component is just a placeholder, we can just run through the
// next one immediately.
return workInProgress.child;
}
case HostComponent:
if (workInProgress.stateNode && typeof config.beginUpdate === 'function') {
config.beginUpdate(workInProgress.stateNode);
@@ -14,6 +14,7 @@
import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { ReifiedYield } from 'ReactReifiedYield';
@@ -157,8 +158,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
markCallback(workInProgress);
}
return null;
case HostContainer:
case HostContainer: {
transferOutput(workInProgress.child, workInProgress);
popContextProvider();
const fiberRoot = (workInProgress.stateNode : FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
// We don't know if a container has updated any children so we always
// need to update it right now. We schedule this side-effect before
// 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>) {
// are invoked.
markUpdate(workInProgress);
return null;
}
case HostComponent:
let newProps = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
+49 -18
View File
@@ -18,6 +18,7 @@ var emptyObject = require('emptyObject');
var invariant = require('invariant');
var {
getComponentName,
isFiberMounted,
} = require('ReactFiberTreeReflection');
var {
ClassComponent,
@@ -64,12 +65,13 @@ exports.hasContextChanged = function() : boolean {
return index > -1 && didPerformWorkStack[index];
};
exports.isContextProvider = function(fiber : Fiber) : boolean {
function isContextProvider(fiber : Fiber) : boolean {
return (
fiber.tag === ClassComponent &&
typeof fiber.stateNode.getChildContext === 'function'
);
};
}
exports.isContextProvider = isContextProvider;
exports.popContextProvider = function() : void {
contextStack[index] = emptyObject;
@@ -77,10 +79,36 @@ exports.popContextProvider = function() : void {
index--;
};
exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
exports.pushTopLevelContextObject = function(context : Object, didChange : boolean) : void {
invariant(index === -1, 'Unexpected context found on stack');
index++;
contextStack[index] = context;
didPerformWorkStack[index] = didChange;
};
function processChildContext(fiber : Fiber, parentContext : Object): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
const childContext = instance.getChildContext();
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber),
contextKey
);
}
if (__DEV__) {
const name = getComponentName(fiber);
const debugID = 0; // TODO: pass a real ID
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
}
return {...parentContext, ...childContext};
}
exports.processChildContext = processChildContext;
exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
const instance = fiber.stateNode;
const memoizedMergedChildContext = instance.__reactInternalMemoizedMergedChildContext;
const canReuseMergedChildContext = !didPerformWork && memoizedMergedChildContext != null;
@@ -88,21 +116,7 @@ exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean)
if (canReuseMergedChildContext) {
mergedContext = memoizedMergedChildContext;
} else {
const childContext = instance.getChildContext();
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber),
contextKey
);
}
if (__DEV__) {
const name = getComponentName(fiber);
const debugID = 0; // TODO: pass a real ID
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
}
mergedContext = {...getUnmaskedContext(), ...childContext};
mergedContext = processChildContext(fiber, getUnmaskedContext());
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
}
@@ -115,3 +129,20 @@ exports.resetContext = function() : void {
index = -1;
};
exports.findCurrentUnmaskedContext = function(fiber: Fiber) : Object {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
invariant(
isFiberMounted(fiber) && fiber.tag === ClassComponent,
'Expected subtree parent to be a mounted class component'
);
let node : ?Fiber = parent;
while (node) {
if (isContextProvider(fiber)) {
return fiber.stateNode.__reactInternalMemoizedMergedChildContext;
}
node = node.return;
}
return emptyObject;
};
@@ -17,6 +17,10 @@ import type { FiberRoot } from 'ReactFiberRoot';
import type { TypeOfWork } from 'ReactTypeOfWork';
import type { PriorityLevel } from 'ReactPriorityLevel';
var {
findCurrentUnmaskedContext,
processChildContext,
} = require('ReactFiberContext');
var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');
@@ -28,6 +32,8 @@ if (__DEV__) {
var { findCurrentHostFiber } = require('ReactFiberTreeReflection');
var getContextForSubtree = require('getContextForSubtree');
export type Deadline = {
timeRemaining : () => number
};
@@ -64,8 +70,8 @@ export type HostConfig<T, P, I, TI, C> = {
};
export type Reconciler<C, I, TI> = {
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
unmountContainer(container : OpaqueNode) : void,
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
/* eslint-disable no-undef */
@@ -81,6 +87,10 @@ export type Reconciler<C, I, TI> = {
findHostInstance(component : Fiber) : I | TI | null,
};
getContextForSubtree._injectFiber(function(fiber : Fiber) {
return processChildContext(fiber, findCurrentUnmaskedContext(fiber));
});
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I, TI> {
var {
@@ -92,8 +102,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
return {
mountContainer(element : ReactElement<any>, containerInfo : C, callback: ?Function) : OpaqueNode {
const root = createFiberRoot(containerInfo);
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
const context = getContextForSubtree(parentComponent);
const root = createFiberRoot(containerInfo, context);
const container = root.current;
if (callback) {
const queue = createUpdateQueue(null);
@@ -117,7 +128,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
return container;
},
updateContainer(element : ReactElement<any>, container : OpaqueNode, callback: ?Function) : void {
updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
// TODO: If this is a nested container, this won't be the root.
const root : FiberRoot = (container.stateNode : any);
if (callback) {
@@ -127,6 +138,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
addCallbackToQueue(queue, callback);
root.callbackList = queue;
}
root.pendingContext = getContextForSubtree(parentComponent);
// TODO: Use pending work/state instead of props.
root.current.pendingProps = element;
+6 -1
View File
@@ -28,9 +28,12 @@ export type FiberRoot = {
nextScheduledRoot: ?FiberRoot,
// Linked list of callbacks to call after updates are committed.
callbackList: ?UpdateQueue,
// Top context object, used by renderSubtreeIntoContainer
context: Object,
pendingContext: ?Object,
};
exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
exports.createFiberRoot = function(containerInfo : any, context : Object) : FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostContainerFiber();
@@ -40,6 +43,8 @@ exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
isScheduled: false,
nextScheduledRoot: null,
callbackList: null,
context: context,
pendingContext: null,
};
uninitializedFiber.stateNode = root;
return root;
@@ -61,6 +61,7 @@ function isFiberMounted(fiber : Fiber) : number {
// that has been unmounted.
return UNMOUNTED;
}
exports.isFiberMounted = isFiberMounted;
exports.isMounted = function(component : ReactComponent<any, any, any>) : boolean {
var fiber : ?Fiber = ReactInstanceMap.get(component);
@@ -0,0 +1,41 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule getContextForSubtree
* @flow
*/
import type { Fiber } from 'ReactFiber';
const ReactInstanceMap = require('ReactInstanceMap');
const emptyObject = require('emptyObject');
const invariant = require('invariant');
let getContextFiber = function(arg) {
invariant(false, 'Missing injection for fiber getContextForSubtree');
};
function getContextForSubtree(parentComponent : ?ReactComponent<any, any, any>) : Object {
if (!parentComponent) {
return emptyObject;
}
const instance = ReactInstanceMap.get(parentComponent);
if (typeof instance.tag === 'number') {
return getContextFiber(instance);
} else {
return instance._processChildContext(instance._context);
}
}
getContextForSubtree._injectFiber = function(fn) {
getContextFiber = fn;
};
module.exports = getContextForSubtree;