380 lines
15 KiB
JavaScript
380 lines
15 KiB
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,
|
|
reconcileChildFibersInPlace = _require.reconcileChildFibersInPlace,
|
|
cloneChildFibers = _require.cloneChildFibers;
|
|
|
|
var _require2 = require('./ReactPriorityLevel'),
|
|
LowPriority = _require2.LowPriority;
|
|
|
|
var ReactTypeOfWork = require('./ReactTypeOfWork');
|
|
var IndeterminateComponent = ReactTypeOfWork.IndeterminateComponent,
|
|
FunctionalComponent = ReactTypeOfWork.FunctionalComponent,
|
|
ClassComponent = ReactTypeOfWork.ClassComponent,
|
|
HostContainer = ReactTypeOfWork.HostContainer,
|
|
HostComponent = ReactTypeOfWork.HostComponent,
|
|
CoroutineComponent = ReactTypeOfWork.CoroutineComponent,
|
|
CoroutineHandlerPhase = ReactTypeOfWork.CoroutineHandlerPhase,
|
|
YieldComponent = ReactTypeOfWork.YieldComponent;
|
|
|
|
var _require3 = require('./ReactPriorityLevel'),
|
|
NoWork = _require3.NoWork,
|
|
OffscreenPriority = _require3.OffscreenPriority;
|
|
|
|
var _require4 = require('./ReactFiberUpdateQueue'),
|
|
createUpdateQueue = _require4.createUpdateQueue,
|
|
addToQueue = _require4.addToQueue,
|
|
addCallbackToQueue = _require4.addCallbackToQueue,
|
|
mergeUpdateQueue = _require4.mergeUpdateQueue;
|
|
|
|
var ReactInstanceMap = require('./ReactInstanceMap');
|
|
|
|
module.exports = function (config, getScheduler) {
|
|
|
|
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
|
// We now have clones. Let's store them as the currently progressed work.
|
|
workInProgress.progressedChild = workInProgress.child;
|
|
workInProgress.progressedPriority = priorityLevel;
|
|
if (current) {
|
|
// We also store it on the current. When the alternate swaps in we can
|
|
// continue from this point.
|
|
current.progressedChild = workInProgress.progressedChild;
|
|
current.progressedPriority = workInProgress.progressedPriority;
|
|
}
|
|
}
|
|
|
|
function reconcileChildren(current, workInProgress, nextChildren) {
|
|
var priorityLevel = workInProgress.pendingWorkPriority;
|
|
reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
|
|
}
|
|
|
|
function reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel) {
|
|
// At this point any memoization is no longer valid since we'll have changed
|
|
// the children.
|
|
workInProgress.memoizedProps = null;
|
|
if (current && current.child === workInProgress.child) {
|
|
// If the current child is the same as the work in progress, it means that
|
|
// we haven't yet started any work on these children. Therefore, we use
|
|
// the clone algorithm to create a copy of all the current children.
|
|
workInProgress.child = reconcileChildFibers(workInProgress, workInProgress.child, nextChildren, priorityLevel);
|
|
} else {
|
|
// If, on the other hand, we don't have a current fiber or if it is
|
|
// already using a clone, that means we've already begun some work on this
|
|
// tree and we can continue where we left off by reconciling against the
|
|
// existing children.
|
|
workInProgress.child = reconcileChildFibersInPlace(workInProgress, workInProgress.child, nextChildren, priorityLevel);
|
|
}
|
|
markChildAsProgressed(current, workInProgress, priorityLevel);
|
|
}
|
|
|
|
function updateFunctionalComponent(current, workInProgress) {
|
|
var fn = workInProgress.type;
|
|
var props = workInProgress.pendingProps;
|
|
|
|
// TODO: Disable this before release, since it is not part of the public API
|
|
// I use this for testing to compare the relative overhead of classes.
|
|
if (typeof fn.shouldComponentUpdate === 'function') {
|
|
if (workInProgress.memoizedProps !== null) {
|
|
if (!fn.shouldComponentUpdate(workInProgress.memoizedProps, props)) {
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
|
}
|
|
}
|
|
}
|
|
|
|
var nextChildren = fn(props);
|
|
reconcileChildren(current, workInProgress, nextChildren);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function scheduleUpdate(fiber, updateQueue, priorityLevel) {
|
|
var _getScheduler = getScheduler(),
|
|
scheduleDeferredWork = _getScheduler.scheduleDeferredWork;
|
|
|
|
fiber.updateQueue = updateQueue;
|
|
// Schedule update on the alternate as well, since we don't know which tree
|
|
// is current.
|
|
if (fiber.alternate) {
|
|
fiber.alternate.updateQueue = updateQueue;
|
|
}
|
|
while (true) {
|
|
if (fiber.pendingWorkPriority === NoWork || fiber.pendingWorkPriority >= priorityLevel) {
|
|
fiber.pendingWorkPriority = priorityLevel;
|
|
}
|
|
if (fiber.alternate) {
|
|
if (fiber.alternate.pendingWorkPriority === NoWork || fiber.alternate.pendingWorkPriority >= priorityLevel) {
|
|
fiber.alternate.pendingWorkPriority = priorityLevel;
|
|
}
|
|
}
|
|
// Duck type root
|
|
if (fiber.stateNode && fiber.stateNode.containerInfo) {
|
|
var root = fiber.stateNode;
|
|
scheduleDeferredWork(root, priorityLevel);
|
|
return;
|
|
}
|
|
if (!fiber['return']) {
|
|
throw new Error('No root!');
|
|
}
|
|
fiber = fiber['return'];
|
|
}
|
|
}
|
|
|
|
// Class component state updater
|
|
var updater = {
|
|
enqueueSetState: function (instance, partialState) {
|
|
var fiber = ReactInstanceMap.get(instance);
|
|
var updateQueue = fiber.updateQueue ? addToQueue(fiber.updateQueue, partialState) : createUpdateQueue(partialState);
|
|
scheduleUpdate(fiber, updateQueue, LowPriority);
|
|
},
|
|
enqueueReplaceState: function (instance, state) {
|
|
var fiber = ReactInstanceMap.get(instance);
|
|
var updateQueue = createUpdateQueue(state);
|
|
updateQueue.isReplace = true;
|
|
scheduleUpdate(fiber, updateQueue, LowPriority);
|
|
},
|
|
enqueueForceUpdate: function (instance) {
|
|
var fiber = ReactInstanceMap.get(instance);
|
|
var updateQueue = fiber.updateQueue || createUpdateQueue(null);
|
|
updateQueue.isForced = true;
|
|
scheduleUpdate(fiber, updateQueue, LowPriority);
|
|
},
|
|
enqueueCallback: function (instance, callback) {
|
|
var fiber = ReactInstanceMap.get(instance);
|
|
var updateQueue = fiber.updateQueue ? fiber.updateQueue : createUpdateQueue(null);
|
|
addCallbackToQueue(updateQueue, callback);
|
|
fiber.updateQueue = updateQueue;
|
|
if (fiber.alternate) {
|
|
fiber.alternate.updateQueue = updateQueue;
|
|
}
|
|
}
|
|
};
|
|
|
|
function updateClassComponent(current, workInProgress) {
|
|
// A class component update is the result of either new props or new state.
|
|
// Account for the possibly of missing pending props by falling back to the
|
|
// memoized props.
|
|
var props = workInProgress.pendingProps;
|
|
if (!props && current) {
|
|
props = current.memoizedProps;
|
|
}
|
|
// Compute the state using the memoized state and the update queue.
|
|
var updateQueue = workInProgress.updateQueue;
|
|
var previousState = current ? current.memoizedState : null;
|
|
var state = updateQueue ? mergeUpdateQueue(updateQueue, previousState, props) : previousState;
|
|
|
|
var instance = workInProgress.stateNode;
|
|
if (!instance) {
|
|
var ctor = workInProgress.type;
|
|
workInProgress.stateNode = instance = new ctor(props);
|
|
state = instance.state || null;
|
|
// The initial state must be added to the update queue in case
|
|
// setState is called before the initial render.
|
|
if (state !== null) {
|
|
workInProgress.updateQueue = createUpdateQueue(state);
|
|
}
|
|
// The instance needs access to the fiber so that it can schedule updates
|
|
ReactInstanceMap.set(instance, workInProgress);
|
|
instance.updater = updater;
|
|
} else if (typeof instance.shouldComponentUpdate === 'function' && !(updateQueue && updateQueue.isForced)) {
|
|
if (workInProgress.memoizedProps !== null) {
|
|
// Reset the props, in case this is a ping-pong case rather than a
|
|
// completed update case. For the completed update case, the instance
|
|
// props will already be the memoizedProps.
|
|
instance.props = workInProgress.memoizedProps;
|
|
instance.state = workInProgress.memoizedState;
|
|
if (!instance.shouldComponentUpdate(props, state)) {
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
|
}
|
|
}
|
|
}
|
|
|
|
instance.props = props;
|
|
instance.state = state;
|
|
var nextChildren = instance.render();
|
|
reconcileChildren(current, workInProgress, nextChildren);
|
|
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateHostComponent(current, workInProgress) {
|
|
var nextChildren = workInProgress.pendingProps.children;
|
|
if (workInProgress.pendingProps.hidden && workInProgress.pendingWorkPriority !== OffscreenPriority) {
|
|
// If this host component is hidden, we can bail out on the children.
|
|
// We'll rerender the children later at the lower priority.
|
|
|
|
// It is unfortunate that we have to do the reconciliation of these
|
|
// children already since that will add them to the tree even though
|
|
// they are not actually done yet. If this is a large set it is also
|
|
// confusing that this takes time to do right now instead of later.
|
|
|
|
if (workInProgress.progressedPriority === OffscreenPriority) {
|
|
// If we already made some progress on the offscreen priority before,
|
|
// then we should continue from where we left off.
|
|
workInProgress.child = workInProgress.progressedChild;
|
|
}
|
|
|
|
// Reconcile the children and stash them for later work.
|
|
reconcileChildrenAtPriority(current, workInProgress, nextChildren, OffscreenPriority);
|
|
workInProgress.child = current ? current.child : null;
|
|
// Abort and don't process children yet.
|
|
return null;
|
|
} else {
|
|
reconcileChildren(current, workInProgress, nextChildren);
|
|
return workInProgress.child;
|
|
}
|
|
}
|
|
|
|
function mountIndeterminateComponent(current, workInProgress) {
|
|
var fn = workInProgress.type;
|
|
var props = workInProgress.pendingProps;
|
|
var value = fn(props);
|
|
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
|
// Proceed under the assumption that this is a class instance
|
|
workInProgress.tag = ClassComponent;
|
|
if (current) {
|
|
current.tag = ClassComponent;
|
|
}
|
|
value = value.render();
|
|
} else {
|
|
// Proceed under the assumption that this is a functional component
|
|
workInProgress.tag = FunctionalComponent;
|
|
if (current) {
|
|
current.tag = FunctionalComponent;
|
|
}
|
|
}
|
|
reconcileChildren(current, workInProgress, value);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateCoroutineComponent(current, workInProgress) {
|
|
var coroutine = workInProgress.pendingProps;
|
|
if (!coroutine) {
|
|
throw new Error('Should be resolved by now');
|
|
}
|
|
reconcileChildren(current, workInProgress, coroutine.children);
|
|
}
|
|
|
|
/*
|
|
function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
|
|
let child = firstChild;
|
|
do {
|
|
// Ensure that the first and last effect of the parent corresponds
|
|
// to the children's first and last effect.
|
|
if (!returnFiber.firstEffect) {
|
|
returnFiber.firstEffect = child.firstEffect;
|
|
}
|
|
if (child.lastEffect) {
|
|
if (returnFiber.lastEffect) {
|
|
returnFiber.lastEffect.nextEffect = child.firstEffect;
|
|
}
|
|
returnFiber.lastEffect = child.lastEffect;
|
|
}
|
|
} while (child = child.sibling);
|
|
}
|
|
*/
|
|
|
|
function bailoutOnAlreadyFinishedWork(current, workInProgress) {
|
|
var priorityLevel = workInProgress.pendingWorkPriority;
|
|
|
|
// TODO: We should ideally be able to bail out early if the children have no
|
|
// more work to do. However, since we don't have a separation of this
|
|
// Fiber's priority and its children yet - we don't know without doing lots
|
|
// of the same work we do anyway. Once we have that separation we can just
|
|
// bail out here if the children has no more work at this priority level.
|
|
// if (workInProgress.priorityOfChildren <= priorityLevel) {
|
|
// // If there are side-effects in these children that have not yet been
|
|
// // committed we need to ensure that they get properly transferred up.
|
|
// if (current && current.child !== workInProgress.child) {
|
|
// reuseChildrenEffects(workInProgress, child);
|
|
// }
|
|
// return null;
|
|
// }
|
|
|
|
cloneChildFibers(current, workInProgress);
|
|
markChildAsProgressed(current, workInProgress, priorityLevel);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function bailoutOnLowPriority(current, workInProgress) {
|
|
if (current) {
|
|
workInProgress.child = current.child;
|
|
workInProgress.memoizedProps = current.memoizedProps;
|
|
workInProgress.output = current.output;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function beginWork(current, workInProgress, priorityLevel) {
|
|
if (workInProgress.pendingWorkPriority === NoWork || workInProgress.pendingWorkPriority > priorityLevel) {
|
|
return bailoutOnLowPriority(current, workInProgress);
|
|
}
|
|
|
|
if (workInProgress.progressedPriority === priorityLevel) {
|
|
// If we have progressed work on this priority level already, we can
|
|
// proceed this that as the child.
|
|
workInProgress.child = workInProgress.progressedChild;
|
|
}
|
|
|
|
if ((workInProgress.pendingProps === null || workInProgress.memoizedProps !== null && workInProgress.pendingProps === workInProgress.memoizedProps) && workInProgress.updateQueue === null) {
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
|
}
|
|
|
|
switch (workInProgress.tag) {
|
|
case IndeterminateComponent:
|
|
return mountIndeterminateComponent(current, workInProgress);
|
|
case FunctionalComponent:
|
|
return updateFunctionalComponent(current, workInProgress);
|
|
case ClassComponent:
|
|
return updateClassComponent(current, workInProgress);
|
|
case HostContainer:
|
|
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
|
|
// A yield component is just a placeholder, we can just run through the
|
|
// next one immediately.
|
|
if (workInProgress.child) {
|
|
return beginWork(workInProgress.child.alternate, workInProgress.child, priorityLevel);
|
|
}
|
|
return null;
|
|
case HostComponent:
|
|
if (workInProgress.stateNode && typeof config.beginUpdate === 'function') {
|
|
config.beginUpdate(workInProgress.stateNode);
|
|
}
|
|
return updateHostComponent(current, workInProgress);
|
|
case CoroutineHandlerPhase:
|
|
// This is a restart. Reset the tag to the initial phase.
|
|
workInProgress.tag = CoroutineComponent;
|
|
// Intentionally fall through since this is now the same.
|
|
case CoroutineComponent:
|
|
updateCoroutineComponent(current, workInProgress);
|
|
// This doesn't take arbitrary time so we could synchronously just begin
|
|
// eagerly do the work of workInProgress.child as an optimization.
|
|
if (workInProgress.child) {
|
|
return beginWork(workInProgress.child.alternate, workInProgress.child, priorityLevel);
|
|
}
|
|
return workInProgress.child;
|
|
case YieldComponent:
|
|
// A yield component is just a placeholder, we can just run through the
|
|
// next one immediately.
|
|
if (workInProgress.sibling) {
|
|
return beginWork(workInProgress.sibling.alternate, workInProgress.sibling, priorityLevel);
|
|
}
|
|
return null;
|
|
default:
|
|
throw new Error('Unknown unit of work tag');
|
|
}
|
|
}
|
|
|
|
return {
|
|
beginWork: beginWork
|
|
};
|
|
}; |