mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fiber: renderSubtreeIntoContainer (#8368)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user