Skip to content

Commit a5d2c74

Browse files
committed
Add more life-cycles to Fiber
This refactors the initialization process so that we can share it with the "module pattern" initialization. There are a few new interesting scenarios unlocked by this. E.g. constructor -> componentWillMount -> shouldComponentUpdate -> componentDidMount when a render is aborted and resumed. If shouldComponentUpdate returns true then we create a new instance instead of trying componentWillMount again or componentWillReceiveProps without being mounted. Another strange thing is that the "previous props and state" during componentWillReceiveProps, shouldComponentUpdate and componentWillUpdate are all the previous render attempt. However, componentDidMount's previous is the props/state at the previous commit. That is because the first three can execute multiple times before a didMount.
1 parent cb2bc0d commit a5d2c74

File tree

2 files changed

+169
-58
lines changed

2 files changed

+169
-58
lines changed

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ var {
4040
NoWork,
4141
OffscreenPriority,
4242
} = require('ReactPriorityLevel');
43-
var {
44-
mergeUpdateQueue,
45-
} = require('ReactFiberUpdateQueue');
4643
var {
4744
Placement,
4845
} = require('ReactTypeOfSideEffect');
@@ -51,7 +48,11 @@ var ReactFiberClassComponent = require('ReactFiberClassComponent');
5148
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) {
5249

5350
const {
54-
mount,
51+
adoptClassInstance,
52+
constructClassInstance,
53+
mountClassInstance,
54+
resumeMountClassInstance,
55+
updateClassInstance,
5556
} = ReactFiberClassComponent(scheduleUpdate);
5657

5758
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
156157
}
157158

158159
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
159-
// A class component update is the result of either new props or new state.
160-
// Account for the possibly of missing pending props by falling back to the
161-
// memoized props.
162-
var props = workInProgress.pendingProps;
163-
if (!props) {
164-
// If there isn't any new props, then we'll reuse the memoized props.
165-
// This could be from already completed work.
166-
props = workInProgress.memoizedProps;
167-
if (!props) {
168-
throw new Error('There should always be pending or memoized props.');
160+
let shouldUpdate;
161+
if (!current) {
162+
if (!workInProgress.stateNode) {
163+
// In the initial pass we might need to construct the instance.
164+
constructClassInstance(workInProgress);
165+
mountClassInstance(workInProgress);
166+
shouldUpdate = true;
167+
} else {
168+
// In a resume, we'll already have an instance we can reuse.
169+
shouldUpdate = resumeMountClassInstance(workInProgress);
169170
}
170-
}
171-
172-
// Compute the state using the memoized state and the update queue.
173-
var updateQueue = workInProgress.updateQueue;
174-
var previousState = workInProgress.memoizedState;
175-
var state;
176-
if (updateQueue) {
177-
state = mergeUpdateQueue(updateQueue, previousState, props);
178171
} else {
179-
state = previousState;
172+
shouldUpdate = updateClassInstance(current, workInProgress);
180173
}
181-
182-
var instance = workInProgress.stateNode;
183-
if (!instance) {
184-
var ctor = workInProgress.type;
185-
workInProgress.stateNode = instance = new ctor(props);
186-
mount(workInProgress, instance);
187-
state = instance.state || null;
188-
} else if (typeof instance.shouldComponentUpdate === 'function' &&
189-
!(updateQueue && updateQueue.isForced)) {
190-
if (workInProgress.memoizedProps !== null) {
191-
// Reset the props, in case this is a ping-pong case rather than a
192-
// completed update case. For the completed update case, the instance
193-
// props will already be the memoizedProps.
194-
instance.props = workInProgress.memoizedProps;
195-
instance.state = workInProgress.memoizedState;
196-
if (!instance.shouldComponentUpdate(props, state)) {
197-
return bailoutOnAlreadyFinishedWork(current, workInProgress);
198-
}
199-
}
174+
if (!shouldUpdate) {
175+
return bailoutOnAlreadyFinishedWork(current, workInProgress);
200176
}
201-
202-
instance.props = props;
203-
instance.state = state;
204-
var nextChildren = instance.render();
177+
// Rerender
178+
const instance = workInProgress.stateNode;
179+
const nextChildren = instance.render();
205180
reconcileChildren(current, workInProgress, nextChildren);
206-
207181
return workInProgress.child;
208182
}
209183

@@ -258,22 +232,21 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
258232
}
259233

260234
function mountIndeterminateComponent(current, workInProgress) {
235+
if (current) {
236+
throw new Error('An indeterminate component should never have mounted.');
237+
}
261238
var fn = workInProgress.type;
262239
var props = workInProgress.pendingProps;
263240
var value = fn(props);
264241
if (typeof value === 'object' && value && typeof value.render === 'function') {
265242
// Proceed under the assumption that this is a class instance
266243
workInProgress.tag = ClassComponent;
267-
if (current) {
268-
current.tag = ClassComponent;
269-
}
244+
adoptClassInstance(workInProgress, value);
245+
mountClassInstance(workInProgress);
270246
value = value.render();
271247
} else {
272248
// Proceed under the assumption that this is a functional component
273249
workInProgress.tag = FunctionalComponent;
274-
if (current) {
275-
current.tag = FunctionalComponent;
276-
}
277250
}
278251
reconcileChildren(current, workInProgress, value);
279252
return workInProgress.child;

src/renderers/shared/fiber/ReactFiberClassComponent.js

Lines changed: 143 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var {
2121
createUpdateQueue,
2222
addToQueue,
2323
addCallbackToQueue,
24+
mergeUpdateQueue,
2425
} = require('ReactFiberUpdateQueue');
2526
var ReactInstanceMap = require('ReactInstanceMap');
2627

@@ -70,20 +71,157 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : Priori
7071
},
7172
};
7273

73-
function mount(workInProgress : Fiber, instance : any) {
74+
function adoptClassInstance(workInProgress : Fiber, instance : any) : void {
75+
instance.updater = updater;
76+
workInProgress.stateNode = instance;
77+
// The instance needs access to the fiber so that it can schedule updates
78+
ReactInstanceMap.set(instance, workInProgress);
79+
}
80+
81+
function constructClassInstance(workInProgress : Fiber) : any {
82+
const ctor = workInProgress.type;
83+
const props = workInProgress.pendingProps;
84+
const instance = new ctor(props);
85+
adoptClassInstance(workInProgress, instance);
86+
return instance;
87+
}
88+
89+
// Invokes the mount life-cycles on a previously never rendered instance.
90+
function mountClassInstance(workInProgress : Fiber) : void {
91+
const instance = workInProgress.stateNode;
92+
7493
const state = instance.state || null;
7594
// The initial state must be added to the update queue in case
7695
// setState is called before the initial render.
7796
if (state !== null) {
7897
workInProgress.updateQueue = createUpdateQueue(state);
7998
}
80-
// The instance needs access to the fiber so that it can schedule updates
81-
ReactInstanceMap.set(instance, workInProgress);
82-
instance.updater = updater;
99+
100+
// A class component update is the result of either new props or new state.
101+
// Account for the possibly of missing pending props by falling back to the
102+
// memoized props.
103+
let props = workInProgress.pendingProps;
104+
if (!props) {
105+
throw new Error('There must be pending props for an initial mount.');
106+
}
107+
108+
instance.props = props;
109+
instance.state = state;
110+
111+
if (typeof instance.componentWillMount === 'function') {
112+
instance.componentWillMount();
113+
// If we had additional state updates during this life-cycle, let's
114+
// process them now.
115+
const updateQueue = workInProgress.updateQueue;
116+
if (updateQueue) {
117+
instance.state = mergeUpdateQueue(updateQueue, state, props);
118+
}
119+
}
120+
}
121+
122+
// Called on a preexisting class instance. Returns false if a resumed render
123+
// could be reused.
124+
function resumeMountClassInstance(workInProgress : Fiber) : boolean {
125+
const instance = workInProgress.stateNode;
126+
const newState = workInProgress.memoizedState;
127+
let newProps = workInProgress.pendingProps;
128+
if (!newProps) {
129+
// If there isn't any new props, then we'll reuse the memoized props.
130+
// This could be from already completed work.
131+
newProps = workInProgress.memoizedProps;
132+
if (!newProps) {
133+
throw new Error('There should always be pending or memoized props.');
134+
}
135+
}
136+
137+
// TODO: Should we deal with a setState that happened after the last
138+
// componentWillMount and before this componentWillMount? Probably
139+
// unsupported anyway.
140+
141+
const updateQueue = workInProgress.updateQueue;
142+
143+
// If this completed, we might be able to just reuse this instance.
144+
if (typeof instance.shouldComponentUpdate === 'function' &&
145+
!(updateQueue && updateQueue.isForced) &&
146+
workInProgress.memoizedProps !== null &&
147+
!instance.shouldComponentUpdate(newProps, newState)) {
148+
return false;
149+
}
150+
151+
// If we didn't bail out we need to construct a new instance. We don't
152+
// want to reuse one that failed to fully mount.
153+
const newInstance = constructClassInstance(workInProgress);
154+
if (typeof newInstance.componentWillMount === 'function') {
155+
newInstance.componentWillMount();
156+
// If we had additional state updates during this life-cycle, let's
157+
// process them now.
158+
const newUpdateQueue = workInProgress.updateQueue;
159+
if (newUpdateQueue) {
160+
instance.state = mergeUpdateQueue(newUpdateQueue, newState, newProps);
161+
}
162+
}
163+
return true;
164+
}
165+
166+
// Invokes the update life-cycles and returns false if it shouldn't rerender.
167+
function updateClassInstance(current : Fiber, workInProgress : Fiber) : boolean {
168+
const instance = workInProgress.stateNode;
169+
170+
const oldProps = current.memoizedProps;
171+
let newProps = workInProgress.pendingProps;
172+
if (!newProps) {
173+
// If there aren't any new props, then we'll reuse the memoized props.
174+
// This could be from already completed work.
175+
newProps = workInProgress.memoizedProps;
176+
if (!newProps) {
177+
throw new Error('There should always be pending or memoized props.');
178+
}
179+
}
180+
181+
// Note: During these life-cycles, instance.props/instance.state are what
182+
// ever the previously attempted to render - not the "current". However,
183+
// during componentDidUpdate we pass the "current" props.
184+
185+
if (oldProps !== newProps) {
186+
if (typeof instance.componentWillReceiveProps === 'function') {
187+
instance.componentWillReceiveProps(newProps);
188+
}
189+
}
190+
191+
// Compute the next state using the memoized state and the update queue.
192+
const updateQueue = workInProgress.updateQueue;
193+
const previousState = workInProgress.memoizedState;
194+
// TODO: Previous state can be null.
195+
let newState;
196+
if (updateQueue) {
197+
newState = mergeUpdateQueue(updateQueue, previousState, newProps);
198+
} else {
199+
newState = previousState;
200+
}
201+
202+
if (typeof instance.shouldComponentUpdate === 'function' &&
203+
!(updateQueue && updateQueue.isForced) &&
204+
workInProgress.memoizedProps !== null &&
205+
!instance.shouldComponentUpdate(newProps, newState)) {
206+
// TODO: Should this get the new props/state updated regardless?
207+
return false;
208+
}
209+
210+
if (typeof instance.componentWillUpdate === 'function') {
211+
instance.componentWillUpdate(newProps, newState);
212+
}
213+
214+
instance.props = newProps;
215+
instance.state = newState;
216+
return true;
83217
}
84218

85219
return {
86-
mount,
220+
adoptClassInstance,
221+
constructClassInstance,
222+
mountClassInstance,
223+
resumeMountClassInstance,
224+
updateClassInstance,
87225
};
88226

89227
};

0 commit comments

Comments
 (0)