Extract commit phase passes into separate functions
By splitting these out, we ensure that they can be optimized by the JS engine.
Andrew Clark committed
Dec 7, 2016 at 11:42 UTC
24a83a5eeb1ccf4da1bdd97166d6c7c94d821bd8
2 files changed
+120
-114
src/renderers/shared/fiber/ReactFiberCommitWork.js
+1
-1
@@ -35,7 +35,7 @@ var {
35
36
module.exports = function<T, P, I, TI, C>(
37
config : HostConfig<T, P, I, TI, C>,
38
- captureError : (failedFiber : Fiber, error: Error) => Fiber | null
38
+ captureError : (failedFiber : Fiber, error: Error) => ?Fiber
39
) {
40
41
const commitUpdate = config.commitUpdate;
src/renderers/shared/fiber/ReactFiberScheduler.js
+119
-113
@@ -87,7 +87,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
87
let nextUnitOfWork : ?Fiber = null;
88
let nextPriorityLevel : PriorityLevel = NoWork;
89
90
- let pendingCommit : Fiber | null = null;
90
+ // The next fiber with an effect, during the commit phase.
91
+ let nextEffect : ?Fiber = null;
92
+
93
+ let pendingCommit : ?Fiber = null;
94
95
// Linked list of roots with scheduled work on them.
96
let nextScheduledRoot : ?FiberRoot = null;
@@ -172,7 +175,97 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
175
return null;
176
}
177
175
- // Returns true if the commit phase finishes without any errors.
178
+ function commitAllHostEffects(finishedWork : Fiber) {
179
+ while (nextEffect) {
180
+ if (nextEffect.effectTag & ContentReset) {
181
+ config.resetTextContent(nextEffect.stateNode);
182
+ }
183
+
184
+ // The following switch statement is only concerned about placement,
185
+ // updates, and deletions. To avoid needing to add a case for every
186
+ // possible bitmap value, we remove the secondary effects from the
187
+ // effect tag and switch on that value.
188
+ let primaryEffectTag = nextEffect.effectTag & ~(Callback | Err | ContentReset);
189
+ switch (primaryEffectTag) {
190
+ case Placement: {
191
+ commitPlacement(nextEffect);
192
+ // Clear the "placement" from effect tag so that we know that this is inserted, before
193
+ // any life-cycles like componentDidMount gets called.
194
+ // TODO: findDOMNode doesn't rely on this any more but isMounted
195
+ // does and isMounted is deprecated anyway so we should be able
196
+ // to kill this.
197
+ nextEffect.effectTag &= ~Placement;
198
+ break;
199
+ }
200
+ case PlacementAndUpdate: {
201
+ // Placement
202
+ commitPlacement(nextEffect);
203
+ // Clear the "placement" from effect tag so that we know that this is inserted, before
204
+ // any life-cycles like componentDidMount gets called.
205
+ nextEffect.effectTag &= ~Placement;
206
+
207
+ // Update
208
+ const current = nextEffect.alternate;
209
+ commitWork(current, nextEffect);
210
+ break;
211
+ }
212
+ case Update: {
213
+ const current = nextEffect.alternate;
214
+ commitWork(current, nextEffect);
215
+ break;
216
+ }
217
+ case Deletion: {
218
+ isUnmounting = true;
219
+ commitDeletion(nextEffect);
220
+ isUnmounting = false;
221
+ break;
222
+ }
223
+ }
224
+ nextEffect = nextEffect.nextEffect;
225
+ }
226
+
227
+ // If the root itself had an effect, we perform that since it is
228
+ // not part of the effect list.
229
+ if (finishedWork.effectTag !== NoEffect) {
230
+ const current = finishedWork.alternate;
231
+ commitWork(current, finishedWork);
232
+ }
233
+ }
234
+
235
+ function commitAllLifeCycles(finishedWork : Fiber) {
236
+ while (nextEffect) {
237
+ const current = nextEffect.alternate;
238
+ // Use Task priority for lifecycle updates
239
+ if (nextEffect.effectTag & (Update | Callback)) {
240
+ commitLifeCycles(current, nextEffect);
241
+ }
242
+
243
+ if (nextEffect.effectTag & Err) {
244
+ commitErrorHandling(nextEffect);
245
+ }
246
+
247
+ const next = nextEffect.nextEffect;
248
+ // Ensure that we clean these up so that we don't accidentally keep them.
249
+ // I'm not actually sure this matters because we can't reset firstEffect
250
+ // and lastEffect since they're on every node, not just the effectful
251
+ // ones. So we have to clean everything as we reuse nodes anyway.
252
+ nextEffect.nextEffect = null;
253
+ // Ensure that we reset the effectTag here so that we can rely on effect
254
+ // tags to reason about the current life-cycle.
255
+ nextEffect = next;
256
+ }
257
+
258
+ // If the root itself had an effect, we perform that since it is
259
+ // not part of the effect list.
260
+ if (finishedWork.effectTag !== NoEffect) {
261
+ const current = finishedWork.alternate;
262
+ commitLifeCycles(current, finishedWork);
263
+ if (finishedWork.effectTag & Err) {
264
+ commitErrorHandling(finishedWork);
265
+ }
266
+ }
267
+ }
268
+
269
function commitAllWork(finishedWork : Fiber) {
270
// We keep track of this so that captureError can collect any boundaries
271
// that capture an error during the commit phase. The reason these aren't
@@ -196,129 +289,42 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
289
290
prepareForCommit();
291
199
- // Commit all the side-effects within a tree.
200
- // First, we'll perform all the host insertions, updates, deletions and
292
+ // Commit all the side-effects within a tree. We'll do this in two passes.
293
+ // The first pass performs all the host insertions, updates, deletions and
294
// ref unmounts.
202
- let effectfulFiber = finishedWork.firstEffect;
203
- pass1: while (true) {
204
- let didHandleRoot = false;
295
+ nextEffect = finishedWork.firstEffect;
296
+ while (true) {
297
try {
206
- while (effectfulFiber) {
207
- if (effectfulFiber.effectTag & ContentReset) {
208
- config.resetTextContent(effectfulFiber.stateNode);
209
- }
210
-
211
- // The following switch statement is only concerned about placement,
212
- // updates, and deletions. To avoid needing to add a case for every
213
- // possible bitmap value, we remove the secondary effects from the
214
- // effect tag and switch on that value.
215
- let primaryEffectTag = effectfulFiber.effectTag & ~(Callback | Err | ContentReset);
216
- switch (primaryEffectTag) {
217
- case Placement: {
218
- commitPlacement(effectfulFiber);
219
- // Clear the "placement" from effect tag so that we know that this is inserted, before
220
- // any life-cycles like componentDidMount gets called.
221
- // TODO: findDOMNode doesn't rely on this any more but isMounted
222
- // does and isMounted is deprecated anyway so we should be able
223
- // to kill this.
224
- effectfulFiber.effectTag &= ~Placement;
225
- break;
226
- }
227
- case PlacementAndUpdate: {
228
- // Placement
229
- commitPlacement(effectfulFiber);
230
- // Clear the "placement" from effect tag so that we know that this is inserted, before
231
- // any life-cycles like componentDidMount gets called.
232
- effectfulFiber.effectTag &= ~Placement;
233
-
234
- // Update
235
- const current = effectfulFiber.alternate;
236
- commitWork(current, effectfulFiber);
237
- break;
238
- }
239
- case Update: {
240
- const current = effectfulFiber.alternate;
241
- commitWork(current, effectfulFiber);
242
- break;
243
- }
244
- case Deletion: {
245
- isUnmounting = true;
246
- commitDeletion(effectfulFiber);
247
- isUnmounting = false;
248
- break;
249
- }
250
- }
251
- effectfulFiber = effectfulFiber.nextEffect;
252
- }
253
-
254
- // Finally if the root itself had an effect, we perform that since it is
255
- // not part of the effect list.
256
- if (!didHandleRoot && finishedWork.effectTag !== NoEffect) {
257
- didHandleRoot = true;
258
- const current = finishedWork.alternate;
259
- commitWork(current, finishedWork);
260
- }
298
+ commitAllHostEffects(finishedWork);
299
} catch (error) {
262
- captureError(effectfulFiber || null, error);
263
- if (effectfulFiber) {
264
- effectfulFiber = effectfulFiber.nextEffect;
265
- }
300
+ captureError(nextEffect, error);
301
// Clean-up
302
isUnmounting = false;
268
- continue pass1;
303
+ if (nextEffect) {
304
+ nextEffect = nextEffect.nextEffect;
305
+ continue;
306
+ }
307
}
308
break;
309
}
310
311
resetAfterCommit();
312
275
- // Next, we'll perform all life-cycles and ref callbacks. Life-cycles
276
- // happens as a separate pass so that all effects in the entire tree have
277
- // already been invoked.
278
- effectfulFiber = finishedWork.firstEffect;
279
- pass2: while (true) {
280
- let didHandleRoot = false;
313
+ // In the second pass we'll perform all life-cycles and ref callbacks.
314
+ // Life-cycles happen as a separate pass so that all placements, updates,
315
+ // and deletions in the entire tree have already been invoked.
316
+ nextEffect = finishedWork.firstEffect;
317
+ while (true) {
318
try {
282
- while (effectfulFiber) {
283
- const current = effectfulFiber.alternate;
284
- // Use Task priority for lifecycle updates
285
- if (effectfulFiber.effectTag & (Update | Callback)) {
286
- commitLifeCycles(current, effectfulFiber);
287
- }
288
-
289
- if (effectfulFiber.effectTag & Err) {
290
- commitErrorHandling(effectfulFiber);
291
- }
292
-
293
- const next = effectfulFiber.nextEffect;
294
- // Ensure that we clean these up so that we don't accidentally keep them.
295
- // I'm not actually sure this matters because we can't reset firstEffect
296
- // and lastEffect since they're on every node, not just the effectful
297
- // ones. So we have to clean everything as we reuse nodes anyway.
298
- effectfulFiber.nextEffect = null;
299
- // Ensure that we reset the effectTag here so that we can rely on effect
300
- // tags to reason about the current life-cycle.
301
- effectfulFiber = next;
302
- }
303
-
304
- // Finally if the root itself had an effect, we perform that since it is
305
- // not part of the effect list.
306
- if (!didHandleRoot && finishedWork.effectTag !== NoEffect) {
307
- didHandleRoot = true;
308
- const current = finishedWork.alternate;
309
- commitLifeCycles(current, finishedWork);
310
- if (finishedWork.effectTag & Err) {
311
- commitErrorHandling(finishedWork);
312
- }
313
- }
319
+ commitAllLifeCycles(finishedWork, nextEffect);
320
} catch (error) {
315
- captureError(effectfulFiber || null, error);
316
- if (effectfulFiber) {
317
- const next = effectfulFiber.nextEffect;
318
- effectfulFiber.nextEffect = null;
319
- effectfulFiber = next;
321
+ captureError(nextEffect || null, error);
322
+ if (nextEffect) {
323
+ const next = nextEffect.nextEffect;
324
+ nextEffect.nextEffect = null;
325
+ nextEffect = next;
326
}
321
- continue pass2;
327
+ continue;
328
}
329
break;
330
}
@@ -716,14 +722,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
722
}
723
724
// Returns the boundary that captured the error, or null if the error is ignored
719
- function captureError(failedWork : Fiber | null, error : Error) : Fiber | null {
725
+ function captureError(failedWork : ?Fiber, error : Error) : ?Fiber {
726
// It is no longer valid because we exited the user code.
727
ReactCurrentOwner.current = null;
728
// It is no longer valid because this unit of work failed.
729
nextUnitOfWork = null;
730
731
// Search for the nearest error boundary.
726
- let boundary : Fiber | null = null;
732
+ let boundary : ?Fiber = null;
733
if (failedWork) {
734
// Host containers are a special case. If the failed work itself is a host
735
// container, then it acts as its own boundary. In all other cases, we