Merge pull request #8206 from acdlite/fibertasklifecycleupdates

[Fiber] Updates from inside componentDidMount/Update should have Task priority
This commit is contained in:
Andrew Clark
2016-11-08 13:28:29 -08:00
committed by GitHub
5 changed files with 67 additions and 382 deletions
+3
View File
@@ -72,6 +72,9 @@ src/renderers/art/__tests__/ReactART-test.js
* resolves refs before componentDidMount
* resolves refs before componentDidUpdate
src/renderers/dom/__tests__/ReactDOMProduction-test.js
* should throw with an error code in production
src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
* should set style attribute when styles exist
* should warn when using hyphenated style names
+1 -13
View File
@@ -453,7 +453,6 @@ src/renderers/dom/__tests__/ReactDOMProduction-test.js
* should use prod React
* should handle a simple flow
* should call lifecycle methods
* should throw with an error code in production
src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
* should render strings as children
@@ -843,18 +842,6 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
* splits deferred work on multiple roots
* works on deferred roots in the order they were scheduled
* handles interleaved deferred and animation work
* performs animation work in animation callback
* schedules deferred work in animation callback
* schedules deferred work and performs animation work in animation callback
* performs animation work and schedules deferred work in animation callback
* performs deferred work in deferred callback if it has time
* schedules deferred work in deferred callback if it runs out of time
* performs animated work in deferred callback if it has time
* performs animated work and deferred work in deferred callback if it has time
* performs deferred and animated work work in deferred callback if it has time
* schedules animated work in deferred callback if it runs out of time
* schedules animated work and deferred work in deferred callback if it runs out of time
* schedules deferred work and animated work in deferred callback if it runs out of time
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* can update child nodes of a host instance
@@ -875,6 +862,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* calls setState callback even if component bails out
* calls componentWillUnmount after a deletion, even if nested
* calls componentDidMount/Update after insertion/update
* schedules sync updates when inside componentDidMount/Update
* invokes ref callbacks after insertion/update/unmount
* supports string refs
@@ -209,7 +209,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
while (effectfulFiber) {
if (effectfulFiber.effectTag & (Update | Callback)) {
const current = effectfulFiber.alternate;
const previousPriorityContext = priorityContext;
priorityContext = TaskPriority;
commitLifeCycles(current, effectfulFiber);
priorityContext = previousPriorityContext;
}
const next = effectfulFiber.nextEffect;
// Ensure that we clean these up so that we don't accidentally keep them.
@@ -449,373 +449,4 @@ describe('ReactIncrementalScheduling', () => {
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]);
});
it('performs animation work in animation callback', () => {
class Foo extends React.Component {
componentDidMount() {
// Animation work that will get performed during animation callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
}
render() {
return <span prop="a:1" />;
}
}
// Schedule animation work
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<Foo />, 'a');
});
// Flushing animation work should flush animation work scheduled inside it
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
});
it('schedules deferred work in animation callback', () => {
class Foo extends React.Component {
componentDidMount() {
// Deferred work that will get scheduled during animation callback
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
}
render() {
return <span prop="a:1" />;
}
}
// Schedule animation work
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<Foo />, 'a');
});
// Flushing animation work should not flush the deferred work
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([]);
// Flush the deferred work
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
});
it('schedules deferred work and performs animation work in animation callback', () => {
let hasScheduled = false;
class Foo extends React.Component {
componentDidMount() {
// Deferred work that will get scheduled during animation callback
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
// Animation work that will get performed during animation callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
});
// Deferred work that will get scheduled during animation callback
ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
hasScheduled = true;
}
render() {
return <span prop="a:1" />;
}
}
// Schedule animation work
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<Foo />, 'a');
});
// Flushing animation work should flush animation work scheduled inside it
ReactNoop.flushAnimationPri();
expect(hasScheduled).toBe(true);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([]);
// Flush the deferred work
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
});
it('performs animation work and schedules deferred work in animation callback', () => {
let hasScheduled = false;
class Foo extends React.Component {
componentDidMount() {
// Animation work that will get performed during animation callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
// Deferred work that will get scheduled during animation callback
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
// Animation work that will get performed during animation callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
});
hasScheduled = true;
}
render() {
return <span prop="a:1" />;
}
}
// Schedule animation work
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<Foo />, 'a');
});
// Flushing animation work should flush animation work scheduled inside it
ReactNoop.flushAnimationPri();
expect(hasScheduled).toBe(true);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
// Flush the deferred work
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
});
it('performs deferred work in deferred callback if it has time', () => {
class Foo extends React.Component {
componentDidMount() {
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flushing deferred work should flush deferred work scheduled inside it
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
});
it('schedules deferred work in deferred callback if it runs out of time', () => {
let hasScheduled = false;
class Foo extends React.Component {
componentDidMount() {
// Deferred work that will get scheduled during deferred callback
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
hasScheduled = true;
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flush just enough deferred work to schedule more deferred work
ReactNoop.flushDeferredPri(20);
expect(hasScheduled).toBe(true);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([]);
// Flush the rest of the deferred work
ReactNoop.flushDeferredPri(15);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
});
it('performs animated work in deferred callback if it has time', () => {
class Foo extends React.Component {
componentDidMount() {
// Animated work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flushing deferred work should flush animated work scheduled inside it
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
});
it('performs animated work and deferred work in deferred callback if it has time', () => {
class Foo extends React.Component {
componentDidMount() {
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
// Animation work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
});
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flushing deferred work should flush both deferred and animated work scheduled inside it
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
});
it('performs deferred and animated work work in deferred callback if it has time', () => {
class Foo extends React.Component {
componentDidMount() {
// Animation work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
// Animation work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
});
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flushing deferred work should flush both deferred and animated work scheduled inside it
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
});
it('schedules animated work in deferred callback if it runs out of time', () => {
let hasScheduled = false;
class Foo extends React.Component {
componentDidMount() {
// Animated work that will get scheduled during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
hasScheduled = true;
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flush just enough deferred work to schedule animated work
ReactNoop.flushDeferredPri(20);
expect(hasScheduled).toBe(true);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([]);
// Flush the rest of work in an animated callback.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
});
it('schedules animated work and deferred work in deferred callback if it runs out of time', () => {
let isScheduled = false;
class Foo extends React.Component {
componentDidMount() {
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
// Animation work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
});
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
isScheduled = true;
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flushing deferred work should schedule both deferred and animated work
ReactNoop.flushDeferredPri(20);
expect(isScheduled).toBe(true);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
expect(ReactNoop.getChildren('d')).toEqual([]);
// Flush the rest of the work
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
});
it('schedules deferred work and animated work in deferred callback if it runs out of time', () => {
let isScheduled = false;
class Foo extends React.Component {
componentDidMount() {
// Animation work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
// Deferred work that will get performed during deferred callback
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
// Animation work that will get performed during deferred callback
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
});
isScheduled = true;
}
render() {
return <span prop="a:1" />;
}
}
// Schedule deferred work
ReactNoop.renderToRootWithID(<Foo />, 'a');
// Flushing deferred work should schedule both deferred and animated work
ReactNoop.flushDeferredPri(20);
expect(isScheduled).toBe(true);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
expect(ReactNoop.getChildren('d')).toEqual([]);
// Flush the rest of the work
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
});
});
@@ -1036,6 +1036,66 @@ describe('ReactIncrementalSideEffects', () => {
});
it('schedules sync updates when inside componentDidMount/Update', () => {
var instance;
var ops = [];
class Foo extends React.Component {
state = { tick: 0 };
componentDidMount() {
ops.push('componentDidMount (before setState): ' + this.state.tick);
this.setState({ tick: 1 });
// We're in a batch. Update hasn't flushed yet.
ops.push('componentDidMount (after setState): ' + this.state.tick);
}
componentDidUpdate() {
ops.push('componentDidUpdate: ' + this.state.tick);
if (this.state.tick === 2) {
ops.push('componentDidUpdate (before setState): ' + this.state.tick);
this.setState({ tick: 3 });
ops.push('componentDidUpdate (after setState): ' + this.state.tick);
// We're in a batch. Update hasn't flushed yet.
}
}
render() {
ops.push('render: ' + this.state.tick);
instance = this;
return <span prop={this.state.tick} />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual([
'render: 0',
'componentDidMount (before setState): 0',
'componentDidMount (after setState): 0',
// If the setState inside componentDidMount were deferred, there would be
// no more ops. Because it has Task priority, we get these ops, too:
'render: 1',
'componentDidUpdate: 1',
]);
ops = [];
instance.setState({ tick: 2 });
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual([
'render: 2',
'componentDidUpdate: 2',
'componentDidUpdate (before setState): 2',
'componentDidUpdate (after setState): 2',
// If the setState inside componentDidUpdate were deferred, there would be
// no more ops. Because it has Task priority, we get these ops, too:
'render: 3',
'componentDidUpdate: 3',
]);
});
it('invokes ref callbacks after insertion/update/unmount', () => {
var classInstance = null;