242 lines
8.8 KiB
JavaScript
242 lines
8.8 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 REACT_ELEMENT_TYPE = require('./ReactElementSymbol');
|
||
|
|
||
|
var _require = require('./ReactCoroutine'),
|
||
|
REACT_COROUTINE_TYPE = _require.REACT_COROUTINE_TYPE,
|
||
|
REACT_YIELD_TYPE = _require.REACT_YIELD_TYPE;
|
||
|
|
||
|
var ReactFiber = require('./ReactFiber');
|
||
|
var ReactReifiedYield = require('./ReactReifiedYield');
|
||
|
|
||
|
var cloneFiber = ReactFiber.cloneFiber,
|
||
|
createFiberFromElement = ReactFiber.createFiberFromElement,
|
||
|
createFiberFromCoroutine = ReactFiber.createFiberFromCoroutine,
|
||
|
createFiberFromYield = ReactFiber.createFiberFromYield;
|
||
|
var createReifiedYield = ReactReifiedYield.createReifiedYield;
|
||
|
|
||
|
|
||
|
var isArray = Array.isArray;
|
||
|
|
||
|
function ChildReconciler(shouldClone) {
|
||
|
function createSubsequentChild(returnFiber, existingChild, previousSibling, newChildren, priority) {
|
||
|
if (typeof newChildren !== 'object' || newChildren === null) {
|
||
|
return previousSibling;
|
||
|
}
|
||
|
|
||
|
switch (newChildren.$$typeof) {
|
||
|
case REACT_ELEMENT_TYPE:
|
||
|
{
|
||
|
var element = newChildren;
|
||
|
if (existingChild && element.type === existingChild.type && element.key === existingChild.key) {
|
||
|
// TODO: This is not sufficient since previous siblings could be new.
|
||
|
// Will fix reconciliation properly later.
|
||
|
var clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
|
||
|
if (!shouldClone) {
|
||
|
// TODO: This might be lowering the priority of nested unfinished work.
|
||
|
clone.pendingWorkPriority = priority;
|
||
|
}
|
||
|
clone.pendingProps = element.props;
|
||
|
clone.sibling = null;
|
||
|
clone['return'] = returnFiber;
|
||
|
previousSibling.sibling = clone;
|
||
|
return clone;
|
||
|
}
|
||
|
var child = createFiberFromElement(element, priority);
|
||
|
previousSibling.sibling = child;
|
||
|
child['return'] = returnFiber;
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
case REACT_COROUTINE_TYPE:
|
||
|
{
|
||
|
var coroutine = newChildren;
|
||
|
var _child = createFiberFromCoroutine(coroutine, priority);
|
||
|
previousSibling.sibling = _child;
|
||
|
_child['return'] = returnFiber;
|
||
|
return _child;
|
||
|
}
|
||
|
|
||
|
case REACT_YIELD_TYPE:
|
||
|
{
|
||
|
var yieldNode = newChildren;
|
||
|
var reifiedYield = createReifiedYield(yieldNode);
|
||
|
var _child2 = createFiberFromYield(yieldNode, priority);
|
||
|
_child2.output = reifiedYield;
|
||
|
previousSibling.sibling = _child2;
|
||
|
_child2['return'] = returnFiber;
|
||
|
return _child2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (isArray(newChildren)) {
|
||
|
var prev = previousSibling;
|
||
|
var existing = existingChild;
|
||
|
for (var i = 0; i < newChildren.length; i++) {
|
||
|
var nextExisting = existing && existing.sibling;
|
||
|
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
|
||
|
if (prev && existing) {
|
||
|
// TODO: This is not correct because there could've been more
|
||
|
// than one sibling consumed but I don't want to return a tuple.
|
||
|
existing = nextExisting;
|
||
|
}
|
||
|
}
|
||
|
return prev;
|
||
|
} else {
|
||
|
// TODO: Throw for unknown children.
|
||
|
return previousSibling;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function createFirstChild(returnFiber, existingChild, newChildren, priority) {
|
||
|
if (typeof newChildren !== 'object' || newChildren === null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
switch (newChildren.$$typeof) {
|
||
|
case REACT_ELEMENT_TYPE:
|
||
|
{
|
||
|
/* $FlowFixMe(>=0.31.0): This is an unsafe cast. Consider adding a type
|
||
|
* annotation to the `newChildren` param of this
|
||
|
* function.
|
||
|
*/
|
||
|
var element = newChildren;
|
||
|
if (existingChild && element.type === existingChild.type && element.key === existingChild.key) {
|
||
|
// Get the clone of the existing fiber.
|
||
|
var clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
|
||
|
if (!shouldClone) {
|
||
|
// TODO: This might be lowering the priority of nested unfinished work.
|
||
|
clone.pendingWorkPriority = priority;
|
||
|
}
|
||
|
clone.pendingProps = element.props;
|
||
|
clone.sibling = null;
|
||
|
clone['return'] = returnFiber;
|
||
|
return clone;
|
||
|
}
|
||
|
var child = createFiberFromElement(element, priority);
|
||
|
child['return'] = returnFiber;
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
case REACT_COROUTINE_TYPE:
|
||
|
{
|
||
|
/* $FlowFixMe(>=0.31.0): No 'handler' property found in object type
|
||
|
*/
|
||
|
var coroutine = newChildren;
|
||
|
var _child3 = createFiberFromCoroutine(coroutine, priority);
|
||
|
_child3['return'] = returnFiber;
|
||
|
return _child3;
|
||
|
}
|
||
|
|
||
|
case REACT_YIELD_TYPE:
|
||
|
{
|
||
|
// A yield results in a fragment fiber whose output is the continuation.
|
||
|
// TODO: When there is only a single child, we can optimize this to avoid
|
||
|
// the fragment.
|
||
|
/* $FlowFixMe(>=0.31.0): No 'continuation' property found in object
|
||
|
* type
|
||
|
*/
|
||
|
var yieldNode = newChildren;
|
||
|
var reifiedYield = createReifiedYield(yieldNode);
|
||
|
var _child4 = createFiberFromYield(yieldNode, priority);
|
||
|
_child4.output = reifiedYield;
|
||
|
_child4['return'] = returnFiber;
|
||
|
return _child4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (isArray(newChildren)) {
|
||
|
var first = null;
|
||
|
var prev = null;
|
||
|
var existing = existingChild;
|
||
|
/* $FlowIssue(>=0.31.0) #12747709
|
||
|
*
|
||
|
* `Array.isArray` is matched syntactically for now until predicate
|
||
|
* support is complete.
|
||
|
*/
|
||
|
for (var i = 0; i < newChildren.length; i++) {
|
||
|
var nextExisting = existing && existing.sibling;
|
||
|
if (prev == null) {
|
||
|
prev = createFirstChild(returnFiber, existing, newChildren[i], priority);
|
||
|
first = prev;
|
||
|
} else {
|
||
|
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
|
||
|
}
|
||
|
if (prev && existing) {
|
||
|
// TODO: This is not correct because there could've been more
|
||
|
// than one sibling consumed but I don't want to return a tuple.
|
||
|
existing = nextExisting;
|
||
|
}
|
||
|
}
|
||
|
return first;
|
||
|
} else {
|
||
|
// TODO: Throw for unknown children.
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: This API won't work because we'll need to transfer the side-effects of
|
||
|
// unmounting children to the returnFiber.
|
||
|
function reconcileChildFibers(returnFiber, currentFirstChild, newChildren, priority) {
|
||
|
return createFirstChild(returnFiber, currentFirstChild, newChildren, priority);
|
||
|
}
|
||
|
|
||
|
return reconcileChildFibers;
|
||
|
}
|
||
|
|
||
|
exports.reconcileChildFibers = ChildReconciler(true);
|
||
|
|
||
|
exports.reconcileChildFibersInPlace = ChildReconciler(false);
|
||
|
|
||
|
function cloneSiblings(current, workInProgress, returnFiber) {
|
||
|
workInProgress['return'] = returnFiber;
|
||
|
while (current.sibling) {
|
||
|
current = current.sibling;
|
||
|
workInProgress = workInProgress.sibling = cloneFiber(current, current.pendingWorkPriority);
|
||
|
workInProgress['return'] = returnFiber;
|
||
|
}
|
||
|
workInProgress.sibling = null;
|
||
|
}
|
||
|
|
||
|
exports.cloneChildFibers = function (current, workInProgress) {
|
||
|
if (!workInProgress.child) {
|
||
|
return;
|
||
|
}
|
||
|
if (current && workInProgress.child === current.child) {
|
||
|
// We use workInProgress.child since that lets Flow know that it can't be
|
||
|
// null since we validated that already. However, as the line above suggests
|
||
|
// they're actually the same thing.
|
||
|
var currentChild = workInProgress.child;
|
||
|
// TODO: This used to reset the pending priority. Not sure if that is needed.
|
||
|
// workInProgress.pendingWorkPriority = current.pendingWorkPriority;
|
||
|
// TODO: The below priority used to be set to NoWork which would've
|
||
|
// dropped work. This is currently unobservable but will become
|
||
|
// observable when the first sibling has lower priority work remaining
|
||
|
// than the next sibling. At that point we should add tests that catches
|
||
|
// this.
|
||
|
var newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
|
||
|
workInProgress.child = newChild;
|
||
|
cloneSiblings(currentChild, newChild, workInProgress);
|
||
|
}
|
||
|
|
||
|
// If there is no alternate, then we don't need to clone the children.
|
||
|
// If the children of the alternate fiber is a different set, then we don't
|
||
|
// need to clone. We need to reset the return fiber though since we'll
|
||
|
// traverse down into them.
|
||
|
var child = workInProgress.child;
|
||
|
while (child) {
|
||
|
child['return'] = workInProgress;
|
||
|
child = child.sibling;
|
||
|
}
|
||
|
};
|