mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Reuse the correct child and side-effects when reusing partial work
We need to use the *other* child because we reset it to the current one on the way up. We also need to reset the first/last side-effects to that of the children so that we're committing the right thing.
This commit is contained in:
@@ -148,6 +148,23 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
} 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<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
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.
|
||||
|
||||
@@ -394,8 +394,6 @@ describe('ReactIncremental', function() {
|
||||
);
|
||||
}
|
||||
|
||||
// Start rendering an update
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" step={0} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
@@ -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 <span prop={props.children} />;
|
||||
}
|
||||
|
||||
var middleContent = (
|
||||
<div>
|
||||
<Bar>Hello</Bar>
|
||||
<Bar>World</Bar>
|
||||
</div>
|
||||
);
|
||||
|
||||
function Foo(props) {
|
||||
return (
|
||||
<div hidden={true}>
|
||||
{
|
||||
props.step === 0 ?
|
||||
<div>
|
||||
<Bar>Hi</Bar>
|
||||
<Bar>{props.text}</Bar>
|
||||
</div>
|
||||
: middleContent
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" step={0} />);
|
||||
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(<Foo text="bar" step={1} />);
|
||||
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(<Foo text="foo" step={1} />);
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user