Merge pull request #6988 from sebmarkbage/newreconciler

[Fiber] Minimize abuse of .alternate
This commit is contained in:
Sebastian Markbåge
2016-06-07 18:42:18 -07:00
4 changed files with 84 additions and 70 deletions
+23 -20
View File
@@ -28,7 +28,7 @@ var {
var ReactFiber = require('ReactFiber');
var ReactReifiedYield = require('ReactReifiedYield');
function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSibling : Fiber, newChildren) : Fiber {
function createSubsequentChild(parent : Fiber, existingChild : ?Fiber, previousSibling : Fiber, newChildren) : Fiber {
if (typeof newChildren !== 'object' || newChildren === null) {
return previousSibling;
}
@@ -36,14 +36,14 @@ function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSi
switch (newChildren.$$typeof) {
case REACT_ELEMENT_TYPE: {
const element = (newChildren : ReactElement<any>);
if (nextReusable &&
element.type === nextReusable.type &&
element.key === nextReusable.key) {
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.
const clone = ReactFiber.cloneFiber(nextReusable);
const clone = ReactFiber.cloneFiber(existingChild);
clone.input = element.props;
clone.child = nextReusable.child;
clone.child = existingChild.child;
clone.sibling = null;
previousSibling.sibling = clone;
return clone;
@@ -75,12 +75,14 @@ function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSi
if (Array.isArray(newChildren)) {
let prev : Fiber = previousSibling;
let existing : ?Fiber = existingChild;
for (var i = 0; i < newChildren.length; i++) {
let reusable = null;
if (prev.alternate) {
reusable = prev.alternate.sibling;
prev = createSubsequentChild(parent, existing, prev, newChildren[i]);
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 = existing.sibling;
}
prev = createSubsequentChild(parent, reusable, prev, newChildren[i]);
}
return prev;
} else {
@@ -89,7 +91,7 @@ function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSi
}
}
function createFirstChild(parent, newChildren) {
function createFirstChild(parent, existingChild, newChildren) {
if (typeof newChildren !== 'object' || newChildren === null) {
return null;
}
@@ -97,7 +99,6 @@ function createFirstChild(parent, newChildren) {
switch (newChildren.$$typeof) {
case REACT_ELEMENT_TYPE: {
const element = (newChildren : ReactElement<any>);
const existingChild : ?Fiber = parent.child;
if (existingChild &&
element.type === existingChild.type &&
element.key === existingChild.key) {
@@ -136,16 +137,18 @@ function createFirstChild(parent, newChildren) {
if (Array.isArray(newChildren)) {
var first : ?Fiber = null;
var prev : ?Fiber = null;
var existing : ?Fiber = existingChild;
for (var i = 0; i < newChildren.length; i++) {
if (prev == null) {
prev = createFirstChild(parent, newChildren[i]);
prev = createFirstChild(parent, existing, newChildren[i]);
first = prev;
} else {
let reusable = null;
if (prev.alternate) {
reusable = prev.alternate.sibling;
}
prev = createSubsequentChild(parent, reusable, prev, newChildren[i]);
prev = createSubsequentChild(parent, existing, prev, newChildren[i]);
}
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 = existing.sibling;
}
}
return first;
@@ -155,6 +158,6 @@ function createFirstChild(parent, newChildren) {
}
}
exports.reconcileChildFibers = function(parent : Fiber, firstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
return createFirstChild(parent, newChildren);
exports.reconcileChildFibers = function(parent : Fiber, currentFirstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
return createFirstChild(parent, currentFirstChild, newChildren);
};
@@ -27,31 +27,30 @@ var {
YieldComponent,
} = ReactTypesOfWork;
function updateFunctionalComponent(workInProgress) {
function reconcileChildren(current, workInProgress, nextChildren) {
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
current ? current.child : null,
nextChildren
);
}
function updateFunctionalComponent(current, workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.input;
console.log('update fn:', fn.name);
var nextChildren = fn(props);
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
nextChildren
);
reconcileChildren(current, workInProgress, nextChildren);
}
function updateHostComponent(workInProgress) {
function updateHostComponent(current, workInProgress) {
console.log('host component', workInProgress.type, typeof workInProgress.input.children === 'string' ? workInProgress.input.children : '');
var nextChildren = workInProgress.input.children;
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
nextChildren
);
reconcileChildren(current, workInProgress, nextChildren);
}
function mountIndeterminateComponent(workInProgress) {
function mountIndeterminateComponent(current, workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.input;
var value = fn(props);
@@ -64,34 +63,30 @@ function mountIndeterminateComponent(workInProgress) {
// Proceed under the assumption that this is a functional component
workInProgress.tag = FunctionalComponent;
}
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
value
);
reconcileChildren(current, workInProgress, value);
}
function updateCoroutineComponent(workInProgress) {
function updateCoroutineComponent(current, workInProgress) {
var coroutine = (workInProgress.input : ?ReactCoroutine);
if (!coroutine) {
throw new Error('Should be resolved by now');
}
console.log('begin coroutine', workInProgress.type.name);
workInProgress.child = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.child,
coroutine.children
);
reconcileChildren(current, workInProgress, coroutine.children);
}
function beginWork(workInProgress : Fiber) : ?Fiber {
const alt = workInProgress.alternate;
if (alt && workInProgress.input === alt.memoizedInput) {
function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
if (current && workInProgress.input === current.memoizedInput) {
// The most likely scenario is that the previous copy of the tree contains
// the same input as the new one. In that case, we can just copy the output
// and children from that node.
workInProgress.output = alt.output;
workInProgress.child = alt.child;
workInProgress.output = current.output;
workInProgress.child = current.child;
workInProgress.stateNode = current.stateNode;
return null;
}
if (workInProgress.input === workInProgress.memoizedInput) {
@@ -101,40 +96,45 @@ function beginWork(workInProgress : Fiber) : ?Fiber {
}
switch (workInProgress.tag) {
case IndeterminateComponent:
mountIndeterminateComponent(workInProgress);
break;
mountIndeterminateComponent(current, workInProgress);
return workInProgress.child;
case FunctionalComponent:
updateFunctionalComponent(workInProgress);
break;
updateFunctionalComponent(current, workInProgress);
return workInProgress.child;
case ClassComponent:
console.log('class component', workInProgress.input.type.name);
break;
return workInProgress.child;
case HostComponent:
updateHostComponent(workInProgress);
break;
updateHostComponent(current, workInProgress);
return workInProgress.child;
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(workInProgress);
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);
return beginWork(
workInProgress.child.alternate,
workInProgress.child
);
}
break;
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);
return beginWork(
workInProgress.sibling.alternate,
workInProgress.sibling
);
}
return null;
default:
throw new Error('Unknown unit of work tag');
}
return workInProgress.child;
}
exports.beginWork = beginWork;
@@ -54,7 +54,7 @@ function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
}
}
function moveCoroutineToHandlerPhase(workInProgress : Fiber) {
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
var coroutine = (workInProgress.input : ?ReactCoroutine);
if (!coroutine) {
throw new Error('Should be resolved by now');
@@ -81,15 +81,16 @@ function moveCoroutineToHandlerPhase(workInProgress : Fiber) {
var props = coroutine.props;
var nextChildren = fn(props, yields);
var currentFirstChild = current ? current.stateNode : null;
workInProgress.stateNode = ReactChildFiber.reconcileChildFibers(
workInProgress,
workInProgress.stateNode,
currentFirstChild,
nextChildren
);
return workInProgress.stateNode;
}
exports.completeWork = function(workInProgress : Fiber) : ?Fiber {
exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
switch (workInProgress.tag) {
case FunctionalComponent:
console.log('/functional component', workInProgress.type.name);
@@ -104,7 +105,7 @@ exports.completeWork = function(workInProgress : Fiber) : ?Fiber {
break;
case CoroutineComponent:
console.log('/coroutine component', workInProgress.input.handler.name);
return moveCoroutineToHandlerPhase(workInProgress);
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
transferOutput(workInProgress.stateNode, workInProgress);
// Reset the tag to now be a first phase coroutine.
@@ -51,7 +51,12 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
while (true) {
var next = completeWork(workInProgress);
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
const next = completeWork(current, workInProgress);
if (next) {
// If completing this work spawned new work, do that next.
return next;
@@ -74,7 +79,12 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
}
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
var next = beginWork(workInProgress);
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
const next = beginWork(current, workInProgress);
if (next) {
// If this spawns new work, do that next.
return next;
@@ -123,8 +133,8 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
// TODO: Unify this with ReactChildFiber. We can't now because the parent
// is passed. Should be doable though. Might require a wrapper don't know.
if (rootFiber && rootFiber.type === element.type && rootFiber.key === element.key) {
nextUnitOfWork = rootFiber;
rootFiber.input = element.props;
nextUnitOfWork = ReactFiber.cloneFiber(rootFiber);
nextUnitOfWork.input = element.props;
return {};
}