diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 984c09de98..b6e38d77cc 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -148,6 +148,23 @@ module.exports = function(config : HostConfig) { } while (child = child.sibling); } + 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 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 @@ -191,7 +208,17 @@ module.exports = function(config : HostConfig) { const priorityLevel = workInProgress.pendingWorkPriority; workInProgress.pendingProps = null; workInProgress.pendingWorkPriority = NoWork; - if (workInProgress.child) { + + workInProgress.firstEffect = null; + workInProgress.nextEffect = null; + workInProgress.lastEffect = null; + + if (workInProgress.child && workInProgress.child.alternate) { + // On the way up here, we reset the child node to be the current one. + // Therefore we have to reuse the alternate. This is super weird. + workInProgress.child = workInProgress.child.alternate; + // Ensure that the effects of reused work are preserved. + reuseChildrenEffects(workInProgress, workInProgress.child); // If we bail out but still has work with the current priority in this // subtree, we need to go find it right now. If we don't, we won't flush // it until the next tick. diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index 4c5b734d13..4158724be2 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -394,8 +394,6 @@ describe('ReactIncremental', function() { ); } - // Start rendering an update - // Init ReactNoop.render(); ReactNoop.flush(); diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js index f5ffa84a45..68360c6288 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js @@ -14,7 +14,7 @@ var React; var ReactNoop; -describe('ReactIncremental', function() { +describe('ReactIncrementalSideEffects', function() { beforeEach(function() { React = require('React'); ReactNoop = require('ReactNoop'); @@ -127,6 +127,66 @@ describe('ReactIncremental', function() { }); + it('can reuse side-effects after being preempted', function() { + + function Bar(props) { + return ; + } + + var middleContent = ( +
+ Hello + World +
+ ); + + function Foo(props) { + return ( + + ); + } + + // Init + ReactNoop.render(); + ReactNoop.flush(); + + expect(ReactNoop.root.children).toEqual([ + div(div(span('Hi'), span('foo'))), + ]); + + // Make a quick update which will schedule low priority work to + // update the middle content. + ReactNoop.render(); + ReactNoop.flushLowPri(30); + + // The tree remains unchanged. + expect(ReactNoop.root.children).toEqual([ + div(div(span('Hi'), span('foo'))), + ]); + + // The first Bar has already completed its update but we'll interupt it to + // render some higher priority work. The middle content will bailout so + // it remains untouched which means that it should reuse it next time. + ReactNoop.render(); + ReactNoop.flush(30); + + // Since we did nothing to the middle subtree during the interuption, + // we should be able to reuse the reconciliation work that we already did + // without restarting. The side-effects should still be replayed. + + expect(ReactNoop.root.children).toEqual([ + div(div(span('Hello'), span('World'))), + ]); + }); it('updates a child even though the old props is empty', function() { function Foo(props) {