mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Merge pull request #8015 from sebmarkbage/fiberclassinit
[Fiber] Add more life-cycles
This commit is contained in:
@@ -40,9 +40,6 @@ var {
|
||||
NoWork,
|
||||
OffscreenPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
var {
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var {
|
||||
Placement,
|
||||
} = require('ReactTypeOfSideEffect');
|
||||
@@ -51,7 +48,11 @@ var ReactFiberClassComponent = require('ReactFiberClassComponent');
|
||||
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) {
|
||||
|
||||
const {
|
||||
mount,
|
||||
adoptClassInstance,
|
||||
constructClassInstance,
|
||||
mountClassInstance,
|
||||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
} = ReactFiberClassComponent(scheduleUpdate);
|
||||
|
||||
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
||||
@@ -156,54 +157,27 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
|
||||
}
|
||||
|
||||
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
|
||||
// A class component update is the result of either new props or new state.
|
||||
// Account for the possibly of missing pending props by falling back to the
|
||||
// memoized props.
|
||||
var props = workInProgress.pendingProps;
|
||||
if (!props) {
|
||||
// If there isn't any new props, then we'll reuse the memoized props.
|
||||
// This could be from already completed work.
|
||||
props = workInProgress.memoizedProps;
|
||||
if (!props) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
let shouldUpdate;
|
||||
if (!current) {
|
||||
if (!workInProgress.stateNode) {
|
||||
// In the initial pass we might need to construct the instance.
|
||||
constructClassInstance(workInProgress);
|
||||
mountClassInstance(workInProgress);
|
||||
shouldUpdate = true;
|
||||
} else {
|
||||
// In a resume, we'll already have an instance we can reuse.
|
||||
shouldUpdate = resumeMountClassInstance(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the state using the memoized state and the update queue.
|
||||
var updateQueue = workInProgress.updateQueue;
|
||||
var previousState = workInProgress.memoizedState;
|
||||
var state;
|
||||
if (updateQueue) {
|
||||
state = mergeUpdateQueue(updateQueue, previousState, props);
|
||||
} else {
|
||||
state = previousState;
|
||||
shouldUpdate = updateClassInstance(current, workInProgress);
|
||||
}
|
||||
|
||||
var instance = workInProgress.stateNode;
|
||||
if (!instance) {
|
||||
var ctor = workInProgress.type;
|
||||
workInProgress.stateNode = instance = new ctor(props);
|
||||
mount(workInProgress, instance);
|
||||
state = instance.state || null;
|
||||
} else if (typeof instance.shouldComponentUpdate === 'function' &&
|
||||
!(updateQueue && updateQueue.isForced)) {
|
||||
if (workInProgress.memoizedProps !== null) {
|
||||
// Reset the props, in case this is a ping-pong case rather than a
|
||||
// completed update case. For the completed update case, the instance
|
||||
// props will already be the memoizedProps.
|
||||
instance.props = workInProgress.memoizedProps;
|
||||
instance.state = workInProgress.memoizedState;
|
||||
if (!instance.shouldComponentUpdate(props, state)) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
}
|
||||
if (!shouldUpdate) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
var nextChildren = instance.render();
|
||||
// Rerender
|
||||
const instance = workInProgress.stateNode;
|
||||
const nextChildren = instance.render();
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
@@ -258,22 +232,21 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
|
||||
}
|
||||
|
||||
function mountIndeterminateComponent(current, workInProgress) {
|
||||
if (current) {
|
||||
throw new Error('An indeterminate component should never have mounted.');
|
||||
}
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var value = fn(props);
|
||||
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
||||
// Proceed under the assumption that this is a class instance
|
||||
workInProgress.tag = ClassComponent;
|
||||
if (current) {
|
||||
current.tag = ClassComponent;
|
||||
}
|
||||
adoptClassInstance(workInProgress, value);
|
||||
mountClassInstance(workInProgress);
|
||||
value = value.render();
|
||||
} else {
|
||||
// Proceed under the assumption that this is a functional component
|
||||
workInProgress.tag = FunctionalComponent;
|
||||
if (current) {
|
||||
current.tag = FunctionalComponent;
|
||||
}
|
||||
}
|
||||
reconcileChildren(current, workInProgress, value);
|
||||
return workInProgress.child;
|
||||
|
||||
@@ -21,6 +21,7 @@ var {
|
||||
createUpdateQueue,
|
||||
addToQueue,
|
||||
addCallbackToQueue,
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
@@ -70,20 +71,155 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : Priori
|
||||
},
|
||||
};
|
||||
|
||||
function mount(workInProgress : Fiber, instance : any) {
|
||||
const state = instance.state || null;
|
||||
// The initial state must be added to the update queue in case
|
||||
// setState is called before the initial render.
|
||||
if (state !== null) {
|
||||
workInProgress.updateQueue = createUpdateQueue(state);
|
||||
}
|
||||
function adoptClassInstance(workInProgress : Fiber, instance : any) : void {
|
||||
instance.updater = updater;
|
||||
workInProgress.stateNode = instance;
|
||||
// The instance needs access to the fiber so that it can schedule updates
|
||||
ReactInstanceMap.set(instance, workInProgress);
|
||||
instance.updater = updater;
|
||||
}
|
||||
|
||||
function constructClassInstance(workInProgress : Fiber) : any {
|
||||
const ctor = workInProgress.type;
|
||||
const props = workInProgress.pendingProps;
|
||||
const instance = new ctor(props);
|
||||
adoptClassInstance(workInProgress, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Invokes the mount life-cycles on a previously never rendered instance.
|
||||
function mountClassInstance(workInProgress : Fiber) : void {
|
||||
const instance = workInProgress.stateNode;
|
||||
|
||||
const state = instance.state || null;
|
||||
|
||||
// A class component update is the result of either new props or new state.
|
||||
// Account for the possibly of missing pending props by falling back to the
|
||||
// memoized props.
|
||||
let props = workInProgress.pendingProps;
|
||||
if (!props) {
|
||||
throw new Error('There must be pending props for an initial mount.');
|
||||
}
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
|
||||
if (typeof instance.componentWillMount === 'function') {
|
||||
instance.componentWillMount();
|
||||
// If we had additional state updates during this life-cycle, let's
|
||||
// process them now.
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue) {
|
||||
instance.state = mergeUpdateQueue(updateQueue, state, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called on a preexisting class instance. Returns false if a resumed render
|
||||
// could be reused.
|
||||
function resumeMountClassInstance(workInProgress : Fiber) : boolean {
|
||||
const instance = workInProgress.stateNode;
|
||||
let newState = workInProgress.memoizedState;
|
||||
let newProps = workInProgress.pendingProps;
|
||||
if (!newProps) {
|
||||
// If there isn't any new props, then we'll reuse the memoized props.
|
||||
// This could be from already completed work.
|
||||
newProps = workInProgress.memoizedProps;
|
||||
if (!newProps) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should we deal with a setState that happened after the last
|
||||
// componentWillMount and before this componentWillMount? Probably
|
||||
// unsupported anyway.
|
||||
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
|
||||
// If this completed, we might be able to just reuse this instance.
|
||||
if (typeof instance.shouldComponentUpdate === 'function' &&
|
||||
!(updateQueue && updateQueue.isForced) &&
|
||||
workInProgress.memoizedProps !== null &&
|
||||
!instance.shouldComponentUpdate(newProps, newState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we didn't bail out we need to construct a new instance. We don't
|
||||
// want to reuse one that failed to fully mount.
|
||||
const newInstance = constructClassInstance(workInProgress);
|
||||
newInstance.props = newProps;
|
||||
newInstance.state = newState = newInstance.state || null;
|
||||
|
||||
if (typeof newInstance.componentWillMount === 'function') {
|
||||
newInstance.componentWillMount();
|
||||
// If we had additional state updates during this life-cycle, let's
|
||||
// process them now.
|
||||
const newUpdateQueue = workInProgress.updateQueue;
|
||||
if (newUpdateQueue) {
|
||||
instance.state = mergeUpdateQueue(newUpdateQueue, newState, newProps);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Invokes the update life-cycles and returns false if it shouldn't rerender.
|
||||
function updateClassInstance(current : Fiber, workInProgress : Fiber) : boolean {
|
||||
const instance = workInProgress.stateNode;
|
||||
|
||||
const oldProps = workInProgress.memoizedProps || current.memoizedProps;
|
||||
let newProps = workInProgress.pendingProps;
|
||||
if (!newProps) {
|
||||
// If there aren't any new props, then we'll reuse the memoized props.
|
||||
// This could be from already completed work.
|
||||
newProps = oldProps;
|
||||
if (!newProps) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
|
||||
// Note: During these life-cycles, instance.props/instance.state are what
|
||||
// ever the previously attempted to render - not the "current". However,
|
||||
// during componentDidUpdate we pass the "current" props.
|
||||
|
||||
if (oldProps !== newProps) {
|
||||
if (typeof instance.componentWillReceiveProps === 'function') {
|
||||
instance.componentWillReceiveProps(newProps);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the next state using the memoized state and the update queue.
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
const previousState = workInProgress.memoizedState;
|
||||
// TODO: Previous state can be null.
|
||||
let newState;
|
||||
if (updateQueue) {
|
||||
newState = mergeUpdateQueue(updateQueue, previousState, newProps);
|
||||
} else {
|
||||
newState = previousState;
|
||||
}
|
||||
|
||||
if (typeof instance.shouldComponentUpdate === 'function' &&
|
||||
!(updateQueue && updateQueue.isForced) &&
|
||||
oldProps !== null &&
|
||||
!instance.shouldComponentUpdate(newProps, newState)) {
|
||||
// TODO: Should this get the new props/state updated regardless?
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof instance.componentWillUpdate === 'function') {
|
||||
instance.componentWillUpdate(newProps, newState);
|
||||
}
|
||||
|
||||
instance.props = newProps;
|
||||
instance.state = newState;
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
mount,
|
||||
adoptClassInstance,
|
||||
constructClassInstance,
|
||||
mountClassInstance,
|
||||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -79,14 +79,13 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
|
||||
}
|
||||
// TODO: This is scanning one root at a time. It should be scanning all
|
||||
// roots for high priority work before moving on to lower priorities.
|
||||
let root = nextScheduledRoot;
|
||||
let highestPriorityRoot = null;
|
||||
let highestPriorityLevel = NoWork;
|
||||
while (root) {
|
||||
if (highestPriorityLevel === NoWork ||
|
||||
highestPriorityLevel > root.current.pendingWorkPriority) {
|
||||
if (root.current.pendingWorkPriority !== NoWork && (
|
||||
highestPriorityLevel === NoWork ||
|
||||
highestPriorityLevel > root.current.pendingWorkPriority)) {
|
||||
highestPriorityLevel = root.current.pendingWorkPriority;
|
||||
highestPriorityRoot = root;
|
||||
}
|
||||
|
||||
@@ -625,10 +625,10 @@ describe('ReactIncremental', () => {
|
||||
// Normally shouldComponentUpdate->false is not enough to determine that we
|
||||
// can safely reuse the old props, but I think in this case it would be ok,
|
||||
// since it is a resume of already started work.
|
||||
// Because of the above we can also not reuse the work of Bar because the
|
||||
// Because of the above we can not reuse the work of Bar because the
|
||||
// rerender of Content will generate a new element which will mean we don't
|
||||
// auto-bail out from Bar.
|
||||
expect(ops).toEqual(['Content', 'Bar', 'Middle']);
|
||||
expect(ops).toEqual(['Bar', 'Middle']);
|
||||
|
||||
});
|
||||
|
||||
@@ -839,8 +839,14 @@ describe('ReactIncremental', () => {
|
||||
it('can call sCU while resuming a partly mounted component', () => {
|
||||
var ops = [];
|
||||
|
||||
var instances = new Set();
|
||||
|
||||
class Bar extends React.Component {
|
||||
state = { y: 'A' };
|
||||
constructor() {
|
||||
super();
|
||||
instances.add(this);
|
||||
}
|
||||
shouldComponentUpdate(newProps, newState) {
|
||||
return this.props.x !== newProps.x ||
|
||||
this.state.y !== newState.y;
|
||||
@@ -855,20 +861,29 @@ describe('ReactIncremental', () => {
|
||||
ops.push('Foo');
|
||||
return [
|
||||
<Bar key="a" x="A" />,
|
||||
<Bar key="b" x="B" />,
|
||||
<Bar key="b" x={props.step === 0 ? 'B' : 'B2'} />,
|
||||
<Bar key="c" x="C" />,
|
||||
<Bar key="d" x="D" />,
|
||||
];
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
expect(ops).toEqual(['Foo', 'Bar:A', 'Bar:B']);
|
||||
ReactNoop.render(<Foo step={0} />);
|
||||
ReactNoop.flushDeferredPri(40);
|
||||
expect(ops).toEqual(['Foo', 'Bar:A', 'Bar:B', 'Bar:C']);
|
||||
|
||||
expect(instances.size).toBe(3);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flushDeferredPri(40);
|
||||
expect(ops).toEqual(['Foo', 'Bar:B', 'Bar:C']);
|
||||
ReactNoop.render(<Foo step={1} />);
|
||||
ReactNoop.flushDeferredPri(50);
|
||||
// A completed and was reused. B completed but couldn't be reused because
|
||||
// props differences. C didn't complete and therefore couldn't be reused.
|
||||
// D never even started so it needed a new instance.
|
||||
expect(ops).toEqual(['Foo', 'Bar:B2', 'Bar:C', 'Bar:D']);
|
||||
|
||||
// We expect each rerender to correspond to a new instance.
|
||||
expect(instances.size).toBe(6);
|
||||
});
|
||||
|
||||
it('gets new props when setting state on a partly updated component', () => {
|
||||
@@ -927,4 +942,296 @@ describe('ReactIncremental', () => {
|
||||
expect(ops).toEqual(['Bar:A-1', 'Baz', 'Baz']);
|
||||
});
|
||||
|
||||
it('calls componentWillMount twice if the initial render is aborted', () => {
|
||||
var ops = [];
|
||||
|
||||
class LifeCycle extends React.Component {
|
||||
state = { x: this.props.x };
|
||||
componentWillMount() {
|
||||
ops.push('componentWillMount:' + this.state.x + '-' + this.props.x);
|
||||
}
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount:' + this.state.x + '-' + this.props.x);
|
||||
}
|
||||
render() {
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
||||
function Trail() {
|
||||
ops.push('Trail');
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
ops.push('App');
|
||||
return (
|
||||
<div>
|
||||
<LifeCycle x={props.x} />
|
||||
<Trail />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App x={0} />);
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'App',
|
||||
'componentWillMount:0-0',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<App x={1} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'App',
|
||||
'componentWillMount:1-1',
|
||||
'Trail',
|
||||
'componentDidMount:1-1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('calls componentWill* twice if an update render is aborted', () => {
|
||||
var ops = [];
|
||||
|
||||
class LifeCycle extends React.Component {
|
||||
componentWillMount() {
|
||||
ops.push('componentWillMount:' + this.props.x);
|
||||
}
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount:' + this.props.x);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
ops.push('componentWillReceiveProps:' + this.props.x + '-' + nextProps.x);
|
||||
}
|
||||
shouldComponentUpdate(nextProps) {
|
||||
ops.push('shouldComponentUpdate:' + this.props.x + '-' + nextProps.x);
|
||||
return true;
|
||||
}
|
||||
componentWillUpdate(nextProps) {
|
||||
ops.push('componentWillUpdate:' + this.props.x + '-' + nextProps.x);
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
ops.push('componentDidUpdate:' + this.props.x + '-' + prevProps.x);
|
||||
}
|
||||
render() {
|
||||
ops.push('render:' + this.props.x);
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
// The sibling is used to confirm that we've completed the first child,
|
||||
// but not yet flushed.
|
||||
ops.push('Sibling');
|
||||
return <span />;
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
ops.push('App');
|
||||
|
||||
return [
|
||||
<LifeCycle key="a" x={props.x} />,
|
||||
<Sibling key="b" />,
|
||||
];
|
||||
}
|
||||
|
||||
ReactNoop.render(<App x={0} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'App',
|
||||
'componentWillMount:0',
|
||||
'render:0',
|
||||
'Sibling',
|
||||
'componentDidMount:0',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<App x={1} />);
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'App',
|
||||
'componentWillReceiveProps:0-1',
|
||||
'shouldComponentUpdate:0-1',
|
||||
'componentWillUpdate:0-1',
|
||||
'render:1',
|
||||
'Sibling',
|
||||
// no componentDidUpdate
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<App x={2} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'App',
|
||||
'componentWillReceiveProps:1-2',
|
||||
'shouldComponentUpdate:1-2',
|
||||
'componentWillUpdate:1-2',
|
||||
'render:2',
|
||||
'Sibling',
|
||||
// When componentDidUpdate finally gets called, it covers both updates.
|
||||
'componentDidUpdate:2-0',
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not call componentWillReceiveProps for state-only updates', () => {
|
||||
var ops = [];
|
||||
|
||||
var instances = [];
|
||||
|
||||
class LifeCycle extends React.Component {
|
||||
state = { x: 0 };
|
||||
tick() {
|
||||
this.setState({
|
||||
x: this.state.x + 1,
|
||||
});
|
||||
}
|
||||
componentWillMount() {
|
||||
instances.push(this);
|
||||
ops.push('componentWillMount:' + this.state.x);
|
||||
}
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount:' + this.state.x);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
ops.push('componentWillReceiveProps');
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
ops.push('shouldComponentUpdate:' + this.state.x + '-' + nextState.x);
|
||||
return true;
|
||||
}
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
ops.push('componentWillUpdate:' + this.state.x + '-' + nextState.x);
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
ops.push('componentDidUpdate:' + this.state.x + '-' + prevState.x);
|
||||
}
|
||||
render() {
|
||||
ops.push('render:' + this.state.x);
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
||||
// This wrap is a bit contrived because we can't pause a completed root and
|
||||
// there is currently an issue where a component can't reuse its render
|
||||
// output unless it fully completed.
|
||||
class Wrap extends React.Component {
|
||||
state = { y: 0 };
|
||||
componentWillMount() {
|
||||
instances.push(this);
|
||||
}
|
||||
tick() {
|
||||
this.setState({
|
||||
y: this.state.y + 1,
|
||||
});
|
||||
}
|
||||
render() {
|
||||
ops.push('Wrap');
|
||||
return <LifeCycle y={this.state.y} />;
|
||||
}
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
// The sibling is used to confirm that we've completed the first child,
|
||||
// but not yet flushed.
|
||||
ops.push('Sibling');
|
||||
return <span />;
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
ops.push('App');
|
||||
return [
|
||||
<Wrap key="a" />,
|
||||
<Sibling key="b" />,
|
||||
];
|
||||
}
|
||||
|
||||
ReactNoop.render(<App y={0} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'App',
|
||||
'Wrap',
|
||||
'componentWillMount:0',
|
||||
'render:0',
|
||||
'Sibling',
|
||||
'componentDidMount:0',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// LifeCycle
|
||||
instances[1].tick();
|
||||
|
||||
ReactNoop.flushDeferredPri(25);
|
||||
|
||||
expect(ops).toEqual([
|
||||
// no componentWillReceiveProps
|
||||
'shouldComponentUpdate:0-1',
|
||||
'componentWillUpdate:0-1',
|
||||
'render:1',
|
||||
// no componentDidUpdate
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// LifeCycle
|
||||
instances[1].tick();
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
// no componentWillReceiveProps
|
||||
'shouldComponentUpdate:1-2',
|
||||
'componentWillUpdate:1-2',
|
||||
'render:2',
|
||||
// When componentDidUpdate finally gets called, it covers both updates.
|
||||
'componentDidUpdate:2-0',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Next we will update props of LifeCycle by updating its parent.
|
||||
|
||||
instances[0].tick();
|
||||
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Wrap',
|
||||
'componentWillReceiveProps',
|
||||
'shouldComponentUpdate:2-2',
|
||||
'componentWillUpdate:2-2',
|
||||
'render:2',
|
||||
// no componentDidUpdate
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Next we will update LifeCycle directly but not with new props.
|
||||
instances[1].tick();
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
// This should not trigger another componentWillReceiveProps because
|
||||
// we never got new props.
|
||||
'shouldComponentUpdate:2-3',
|
||||
'componentWillUpdate:2-3',
|
||||
'render:3',
|
||||
'componentDidUpdate:3-2',
|
||||
]);
|
||||
|
||||
// TODO: Test that we get the expected values for the same scenario with
|
||||
// incomplete parents.
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -598,7 +598,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
),
|
||||
]);
|
||||
|
||||
expect(ops).toEqual(['Baz', 'Bar', 'Baz', 'Bar', 'Bar']);
|
||||
expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar']);
|
||||
});
|
||||
|
||||
it('deprioritizes setStates that happens within a deprioritized tree', () => {
|
||||
|
||||
Reference in New Issue
Block a user