react-dom
Version:
React package for working with the DOM.
298 lines (274 loc) • 11.8 kB
JavaScript
/**
* 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.
*
*
*/
'use strict';
var _require = require('./ReactChildFiber'),
reconcileChildFibers = _require.reconcileChildFibers;
var _require2 = require('./ReactFiberContext'),
popContextProvider = _require2.popContextProvider;
var ReactTypeOfWork = require('./ReactTypeOfWork');
var ReactTypeOfSideEffect = require('./ReactTypeOfSideEffect');
var IndeterminateComponent = ReactTypeOfWork.IndeterminateComponent,
FunctionalComponent = ReactTypeOfWork.FunctionalComponent,
ClassComponent = ReactTypeOfWork.ClassComponent,
HostRoot = ReactTypeOfWork.HostRoot,
HostComponent = ReactTypeOfWork.HostComponent,
HostText = ReactTypeOfWork.HostText,
HostPortal = ReactTypeOfWork.HostPortal,
CoroutineComponent = ReactTypeOfWork.CoroutineComponent,
CoroutineHandlerPhase = ReactTypeOfWork.CoroutineHandlerPhase,
YieldComponent = ReactTypeOfWork.YieldComponent,
Fragment = ReactTypeOfWork.Fragment;
var Update = ReactTypeOfSideEffect.Update;
if (process.env.NODE_ENV !== 'production') {
var ReactDebugCurrentFiber = require('./ReactDebugCurrentFiber');
}
module.exports = function (config, hostContext) {
var createInstance = config.createInstance,
createTextInstance = config.createTextInstance,
appendInitialChild = config.appendInitialChild,
finalizeInitialChildren = config.finalizeInitialChildren,
prepareUpdate = config.prepareUpdate;
var getRootHostContainer = hostContext.getRootHostContainer,
popHostContext = hostContext.popHostContext,
getHostContext = hostContext.getHostContext,
popHostContainer = hostContext.popHostContainer;
function markUpdate(workInProgress) {
// Tag the fiber with an update effect. This turns a Placement into
// an UpdateAndPlacement.
workInProgress.effectTag |= Update;
}
function appendAllYields(yields, workInProgress) {
var node = workInProgress.child;
while (node) {
if (node.tag === HostComponent || node.tag === HostText || node.tag === HostPortal) {
throw new Error('A coroutine cannot have host component children.');
} else if (node.tag === YieldComponent) {
yields.push(node.type);
} else if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node.child['return'] = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (!node.sibling) {
if (!node['return'] || node['return'] === workInProgress) {
return;
}
node = node['return'];
}
node.sibling['return'] = node['return'];
node = node.sibling;
}
}
function moveCoroutineToHandlerPhase(current, workInProgress) {
var coroutine = workInProgress.pendingProps;
if (!coroutine) {
throw new Error('Should be resolved by now');
}
// First step of the coroutine has completed. Now we need to do the second.
// TODO: It would be nice to have a multi stage coroutine represented by a
// single component, or at least tail call optimize nested ones. Currently
// that requires additional fields that we don't want to add to the fiber.
// So this requires nested handlers.
// Note: This doesn't mutate the alternate node. I don't think it needs to
// since this stage is reset for every pass.
workInProgress.tag = CoroutineHandlerPhase;
// Build up the yields.
// TODO: Compare this to a generator or opaque helpers like Children.
var yields = [];
appendAllYields(yields, workInProgress);
var fn = coroutine.handler;
var props = coroutine.props;
var nextChildren = fn(props, yields);
var currentFirstChild = current ? current.stateNode : null;
// Inherit the priority of the returnFiber.
var priority = workInProgress.pendingWorkPriority;
workInProgress.stateNode = reconcileChildFibers(workInProgress, currentFirstChild, nextChildren, priority);
return workInProgress.stateNode;
}
function appendAllChildren(parent, workInProgress) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
var node = workInProgress.child;
while (node) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (!node.sibling) {
if (!node['return'] || node['return'] === workInProgress) {
return;
}
node = node['return'];
}
node = node.sibling;
}
}
function completeWork(current, workInProgress) {
if (process.env.NODE_ENV !== 'production') {
ReactDebugCurrentFiber.current = workInProgress;
}
switch (workInProgress.tag) {
case FunctionalComponent:
workInProgress.memoizedProps = workInProgress.pendingProps;
return null;
case ClassComponent:
{
// We are leaving this subtree, so pop context if any.
popContextProvider(workInProgress);
// Don't use the state queue to compute the memoized state. We already
// merged it and assigned it to the instance. Transfer it from there.
// Also need to transfer the props, because pendingProps will be null
// in the case of an update.
var instance = workInProgress.stateNode;
workInProgress.memoizedState = instance.state;
workInProgress.memoizedProps = instance.props;
return null;
}
case HostRoot:
{
// TODO: Pop the host container after #8607 lands.
workInProgress.memoizedProps = workInProgress.pendingProps;
var fiberRoot = workInProgress.stateNode;
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
return null;
}
case HostComponent:
popHostContext(workInProgress);
var type = workInProgress.type;
var newProps = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
var oldProps = current.memoizedProps;
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
if (!newProps) {
newProps = workInProgress.memoizedProps || oldProps;
}
var _instance = workInProgress.stateNode;
var currentHostContext = getHostContext();
if (prepareUpdate(_instance, type, oldProps, newProps, currentHostContext)) {
// This returns true if there was something to update.
markUpdate(workInProgress);
}
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts.');
} else {
// This can happen when we abort work.
return null;
}
}
var rootContainerInstance = getRootHostContainer();
var _currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
var _instance2 = createInstance(type, newProps, rootContainerInstance, _currentHostContext, workInProgress);
appendAllChildren(_instance2, workInProgress);
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(_instance2, type, newProps, rootContainerInstance)) {
workInProgress.effectTag |= Update;
}
workInProgress.stateNode = _instance2;
if (workInProgress.ref) {
// If there is a ref on a host node we need to schedule a callback
markUpdate(workInProgress);
}
}
workInProgress.memoizedProps = newProps;
return null;
case HostText:
var newText = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
var oldText = current.memoizedProps;
if (newText === null) {
// If this was a bail out we need to fall back to memoized text.
// This works the same way as HostComponent.
newText = workInProgress.memoizedProps;
if (newText === null) {
newText = oldText;
}
}
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
if (oldText !== newText) {
markUpdate(workInProgress);
}
} else {
if (typeof newText !== 'string') {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts.');
} else {
// This can happen when we abort work.
return null;
}
}
var _rootContainerInstance = getRootHostContainer();
var _currentHostContext2 = getHostContext();
var textInstance = createTextInstance(newText, _rootContainerInstance, _currentHostContext2, workInProgress);
workInProgress.stateNode = textInstance;
}
workInProgress.memoizedProps = newText;
return null;
case CoroutineComponent:
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
workInProgress.memoizedProps = workInProgress.pendingProps;
// Reset the tag to now be a first phase coroutine.
workInProgress.tag = CoroutineComponent;
return null;
case YieldComponent:
// Does nothing.
return null;
case Fragment:
workInProgress.memoizedProps = workInProgress.pendingProps;
return null;
case HostPortal:
// TODO: Only mark this as an update if we have any pending callbacks.
markUpdate(workInProgress);
workInProgress.memoizedProps = workInProgress.pendingProps;
popHostContainer(workInProgress);
return null;
// Error cases
case IndeterminateComponent:
throw new Error('An indeterminate component should have become determinate before completing.');
default:
throw new Error('Unknown unit of work tag');
}
}
return {
completeWork: completeWork
};
};