From 648d6e190cf44870ab62a0e8a65189975ef44ee6 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 20 Jan 2017 22:21:58 -0800 Subject: [PATCH] Swap .child and .stateNode in coroutines It is slightly more useful this way because when we want to find host nodes we typically want to do so in the second phase. That's the real tree where as the first phase is more of a virtual part of the tree. --- .../shared/fiber/ReactFiberBeginWork.js | 42 ++++++++++++++++++- .../shared/fiber/ReactFiberCommitWork.js | 4 -- .../shared/fiber/ReactFiberCompleteWork.js | 23 +++++++--- .../shared/fiber/ReactFiberScheduler.js | 2 + .../shared/fiber/ReactFiberTreeReflection.js | 1 - .../fiber/__tests__/ReactCoroutine-test.js | 4 +- src/test/ReactTestUtils.js | 1 - 7 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index dbbb914669..67caa03ad5 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -518,11 +518,49 @@ module.exports = function( } else if (nextCoroutine === null || workInProgress.memoizedProps === nextCoroutine) { return bailoutOnAlreadyFinishedWork(current, workInProgress); } - reconcileChildren(current, workInProgress, nextCoroutine.children); + + const nextChildren = nextCoroutine.children; + const priorityLevel = workInProgress.pendingWorkPriority; + + // The following is a fork of reconcileChildrenAtPriority but using + // stateNode to store the child. + + // At this point any memoization is no longer valid since we'll have changed + // the children. + workInProgress.memoizedProps = null; + if (!current) { + workInProgress.stateNode = mountChildFibersInPlace( + workInProgress, + workInProgress.stateNode, + nextChildren, + priorityLevel + ); + } else if (current.child === workInProgress.child) { + clearDeletions(workInProgress); + + workInProgress.stateNode = reconcileChildFibers( + workInProgress, + workInProgress.stateNode, + nextChildren, + priorityLevel + ); + + transferDeletions(workInProgress); + } else { + workInProgress.stateNode = reconcileChildFibersInPlace( + workInProgress, + workInProgress.stateNode, + nextChildren, + priorityLevel + ); + + transferDeletions(workInProgress); + } + memoizeProps(workInProgress, nextCoroutine); // This doesn't take arbitrary time so we could synchronously just begin // eagerly do the work of workInProgress.child as an optimization. - return workInProgress.child; + return workInProgress.stateNode; } function updatePortalComponent(current, workInProgress) { diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 37bef63964..0dd429cdda 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -136,7 +136,6 @@ module.exports = function( while (node.tag !== HostComponent && node.tag !== HostText) { // If it is not host node and, we might have a host node inside it. // Try to search down until we find one. - // TODO: For coroutines, this will have to search the stateNode. if (node.effectTag & Placement) { // If we don't have a child, try the siblings instead. continue siblings; @@ -198,7 +197,6 @@ module.exports = function( // 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.child.return = node; node = node.child; continue; @@ -229,7 +227,6 @@ module.exports = function( // Visit children because they may contain more composite or host nodes. // Skip portals because commitUnmount() currently visits them recursively. if (node.child && node.tag !== HostPortal) { - // TODO: Coroutines need to visit the stateNode. node.child.return = node; node = node.child; continue; @@ -273,7 +270,6 @@ module.exports = function( commitUnmount(node); // Visit children because we may find more host components below. if (node.child) { - // TODO: Coroutines need to visit the stateNode. node.child.return = node; node = node.child; continue; diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 776db30ecc..08644759ca 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -66,6 +66,18 @@ module.exports = function( popHostContainer, } = hostContext; + 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 markUpdate(workInProgress : Fiber) { // Tag the fiber with an update effect. This turns a Placement into // an UpdateAndPlacement. @@ -73,7 +85,7 @@ module.exports = function( } function appendAllYields(yields : Array, workInProgress : Fiber) { - let node = workInProgress.child; + let node = workInProgress.stateNode; while (node) { if (node.tag === HostComponent || node.tag === HostText || node.tag === HostPortal) { @@ -81,7 +93,6 @@ module.exports = function( } 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; @@ -123,16 +134,17 @@ module.exports = function( var props = coroutine.props; var nextChildren = fn(props, yields); - var currentFirstChild = current ? current.stateNode : null; + var currentFirstChild = current ? current.child : null; // Inherit the priority of the returnFiber. const priority = workInProgress.pendingWorkPriority; - workInProgress.stateNode = reconcileChildFibers( + workInProgress.child = reconcileChildFibers( workInProgress, currentFirstChild, nextChildren, priority ); - return workInProgress.stateNode; + markChildAsProgressed(current, workInProgress, priority); + return workInProgress.child; } function appendAllChildren(parent : I, workInProgress : Fiber) { @@ -147,7 +159,6 @@ module.exports = function( // 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; } diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 1b2fc17004..7cdf1b917d 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -435,6 +435,8 @@ module.exports = function(config : HostConfig { expect(ops).toEqual([ 'Unmount Parent', - // TODO: This should happen in the order Child, Continuation which it - // will once we swap stateNode and child positions of these. - 'Unmount Continuation', 'Unmount Child', + 'Unmount Continuation', ]); }); diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 1a393d426b..7a01f13cb7 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -97,7 +97,6 @@ function findAllInRenderedFiberTreeInternal(fiber, test) { } } if (node.child) { - // TODO: Coroutines need to visit the stateNode. node.child.return = node; node = node.child; continue;