7
* @flow
8
*/
9
10
-import type {HostConfig, Deadline} from 'react-reconciler';
10
import type {Fiber} from './ReactFiber';
11
import type {FiberRoot, Batch} from './ReactFiberRoot';
13
-import type {HydrationContext} from './ReactFiberHydrationContext';
12
import type {ExpirationTime} from './ReactFiberExpirationTime';
13
14
import ReactErrorUtils from 'shared/ReactErrorUtils';
43
replayFailedUnitOfWorkWithInvokeGuardedCallback,
44
warnAboutDeprecatedLifecycles,
45
} from 'shared/ReactFeatureFlags';
48
-import {createProfilerTimer} from './ReactProfilerTimer';
46
import getComponentName from 'shared/getComponentName';
47
import invariant from 'fbjs/lib/invariant';
48
import warning from 'fbjs/lib/warning';
49
53
-import ReactFiberBeginWork from './ReactFiberBeginWork';
54
-import ReactFiberCompleteWork from './ReactFiberCompleteWork';
55
-import ReactFiberUnwindWork from './ReactFiberUnwindWork';
56
-import ReactFiberCommitWork from './ReactFiberCommitWork';
57
-import ReactFiberHostContext from './ReactFiberHostContext';
58
-import ReactFiberHydrationContext from './ReactFiberHydrationContext';
50
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
51
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
52
+import {
53
+ now,
54
+ scheduleDeferredCallback,
55
+ cancelDeferredCallback,
56
+ prepareForCommit,
57
+ resetAfterCommit,
58
+} from './ReactFiberHostConfig';
59
import {
60
markPendingPriorityLevel,
61
markCommittedPriorityLevels,
93
computeExpirationBucket,
94
} from './ReactFiberExpirationTime';
95
import {AsyncMode, ProfileMode} from './ReactTypeOfMode';
98
-import ReactFiberLegacyContext from './ReactFiberContext';
99
-import ReactFiberNewContext from './ReactFiberNewContext';
96
import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
97
import {createCapturedValue} from './ReactCapturedValue';
102
-import ReactFiberStack from './ReactFiberStack';
98
+import {
99
+ popTopLevelContextObject as popTopLevelLegacyContextObject,
100
+ popContextProvider as popLegacyContextProvider,
101
+} from './ReactFiberContext';
102
+import {popProvider} from './ReactFiberNewContext';
103
+import {popHostContext, popHostContainer} from './ReactFiberHostContext';
104
+import {
105
+ checkActualRenderTimeStackEmpty,
106
+ pauseActualRenderTimerIfRunning,
107
+ recordElapsedBaseRenderTimeIfRunning,
108
+ resetActualRenderTimer,
109
+ resumeActualRenderTimerIfPaused,
110
+ startBaseRenderTimer,
111
+ stopBaseRenderTimerIfRunning,
112
+} from './ReactProfilerTimer';
113
+import {
114
+ checkThatStackIsEmpty,
115
+ resetStackAfterFatalErrorInDev,
116
+} from './ReactFiberStack';
117
+import {beginWork} from './ReactFiberBeginWork';
118
+import {completeWork} from './ReactFiberCompleteWork';
119
+import {
120
+ throwException,
121
+ unwindWork,
122
+ unwindInterruptedWork,
123
+ createRootErrorUpdate,
124
+ createClassErrorUpdate,
125
+} from './ReactFiberUnwindWork';
126
+import {
127
+ commitBeforeMutationLifeCycles,
128
+ commitResetTextContent,
129
+ commitPlacement,
130
+ commitDeletion,
131
+ commitWork,
132
+ commitLifeCycles,
133
+ commitAttachRef,
134
+ commitDetachRef,
135
+} from './ReactFiberCommitWork';
136
+
137
+export type Deadline = {
138
+ timeRemaining: () => number,
139
+ didTimeout: boolean,
140
+};
141
142
export type Thenable = {
143
then(resolve: () => mixed, reject?: () => mixed): mixed,
206
};
207
}
208
171
-export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
172
- config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
173
-) {
174
- const {
175
- now,
176
- scheduleDeferredCallback,
177
- cancelDeferredCallback,
178
- prepareForCommit,
179
- resetAfterCommit,
180
- } = config;
181
- const stack = ReactFiberStack();
182
- const hostContext = ReactFiberHostContext(config, stack);
183
- const legacyContext = ReactFiberLegacyContext(stack);
184
- const newContext = ReactFiberNewContext(stack, config.isPrimaryRenderer);
185
- const profilerTimer = createProfilerTimer(now);
186
- const {popHostContext, popHostContainer} = hostContext;
187
- const {
188
- popTopLevelContextObject: popTopLevelLegacyContextObject,
189
- popContextProvider: popLegacyContextProvider,
190
- } = legacyContext;
191
- const {popProvider} = newContext;
192
- const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
193
- config,
194
- );
195
- const {beginWork} = ReactFiberBeginWork(
196
- config,
197
- hostContext,
198
- legacyContext,
199
- newContext,
200
- hydrationContext,
201
- scheduleWork,
202
- computeExpirationForFiber,
203
- profilerTimer,
204
- recalculateCurrentTime,
205
- );
206
- const {completeWork} = ReactFiberCompleteWork(
207
- config,
208
- hostContext,
209
- legacyContext,
210
- newContext,
211
- hydrationContext,
212
- profilerTimer,
213
- );
214
- const {
215
- throwException,
216
- unwindWork,
217
- unwindInterruptedWork,
218
- createRootErrorUpdate,
219
- createClassErrorUpdate,
220
- } = ReactFiberUnwindWork(
221
- config,
222
- hostContext,
223
- legacyContext,
224
- newContext,
225
- scheduleWork,
226
- computeExpirationForFiber,
227
- recalculateCurrentTime,
228
- markLegacyErrorBoundaryAsFailed,
229
- isAlreadyFailedLegacyErrorBoundary,
230
- onUncaughtError,
231
- profilerTimer,
232
- suspendRoot,
233
- retrySuspendedRoot,
234
- );
235
- const {
236
- commitBeforeMutationLifeCycles,
237
- commitResetTextContent,
238
- commitPlacement,
239
- commitDeletion,
240
- commitWork,
241
- commitLifeCycles,
242
- commitAttachRef,
243
- commitDetachRef,
244
- } = ReactFiberCommitWork(
245
- config,
246
- onCommitPhaseError,
247
- scheduleWork,
248
- computeExpirationForFiber,
249
- markLegacyErrorBoundaryAsFailed,
250
- recalculateCurrentTime,
251
- );
252
-
253
- const {
254
- checkActualRenderTimeStackEmpty,
255
- pauseActualRenderTimerIfRunning,
256
- recordElapsedBaseRenderTimeIfRunning,
257
- resetActualRenderTimer,
258
- resumeActualRenderTimerIfPaused,
259
- startBaseRenderTimer,
260
- stopBaseRenderTimerIfRunning,
261
- } = profilerTimer;
262
-
263
- // Represents the current time in ms.
264
- const originalStartTimeMs = now();
265
- let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
266
- let mostRecentCurrentTimeMs: number = originalStartTimeMs;
267
-
268
- // Used to ensure computeUniqueAsyncExpiration is monotonically increases.
269
- let lastUniqueAsyncExpiration: number = 0;
270
-
271
- // Represents the expiration time that incoming updates should use. (If this
272
- // is NoWork, use the default strategy: async updates in async mode, sync
273
- // updates in sync mode.)
274
- let expirationContext: ExpirationTime = NoWork;
275
-
276
- let isWorking: boolean = false;
277
-
278
- // The next work in progress fiber that we're currently working on.
279
- let nextUnitOfWork: Fiber | null = null;
280
- let nextRoot: FiberRoot | null = null;
281
- // The time at which we're currently rendering work.
282
- let nextRenderExpirationTime: ExpirationTime = NoWork;
283
- let nextLatestTimeoutMs: number = -1;
284
- let nextRenderIsExpired: boolean = false;
285
-
286
- // The next fiber with an effect that we're currently committing.
287
- let nextEffect: Fiber | null = null;
288
-
289
- let isCommitting: boolean = false;
290
-
291
- let isRootReadyForCommit: boolean = false;
292
-
293
- let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
209
+// Represents the current time in ms.
210
+const originalStartTimeMs = now();
211
+let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
212
+let mostRecentCurrentTimeMs: number = originalStartTimeMs;
213
+
214
+// Used to ensure computeUniqueAsyncExpiration is monotonically increases.
215
+let lastUniqueAsyncExpiration: number = 0;
216
+
217
+// Represents the expiration time that incoming updates should use. (If this
218
+// is NoWork, use the default strategy: async updates in async mode, sync
219
+// updates in sync mode.)
220
+let expirationContext: ExpirationTime = NoWork;
221
+
222
+let isWorking: boolean = false;
223
+
224
+// The next work in progress fiber that we're currently working on.
225
+let nextUnitOfWork: Fiber | null = null;
226
+let nextRoot: FiberRoot | null = null;
227
+// The time at which we're currently rendering work.
228
+let nextRenderExpirationTime: ExpirationTime = NoWork;
229
+let nextLatestTimeoutMs: number = -1;
230
+let nextRenderIsExpired: boolean = false;
231
+
232
+// The next fiber with an effect that we're currently committing.
233
+let nextEffect: Fiber | null = null;
234
+
235
+let isCommitting: boolean = false;
236
+
237
+let isRootReadyForCommit: boolean = false;
238
+
239
+let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
240
+
241
+// Used for performance tracking.
242
+let interruptedBy: Fiber | null = null;
243
+
244
+let stashedWorkInProgressProperties;
245
+let replayUnitOfWork;
246
+let isReplayingFailedUnitOfWork;
247
+let originalReplayError;
248
+let rethrowOriginalError;
249
+if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
250
+ stashedWorkInProgressProperties = null;
251
+ isReplayingFailedUnitOfWork = false;
252
+ originalReplayError = null;
253
+ replayUnitOfWork = (
254
+ failedUnitOfWork: Fiber,
255
+ thrownValue: mixed,
256
+ isAsync: boolean,
257
+ ) => {
258
+ if (
259
+ thrownValue !== null &&
260
+ typeof thrownValue === 'object' &&
261
+ typeof thrownValue.then === 'function'
262
+ ) {
263
+ // Don't replay promises. Treat everything else like an error.
264
+ // TODO: Need to figure out a different strategy if/when we add
265
+ // support for catching other types.
266
+ return;
267
+ }
268
295
- // Used for performance tracking.
296
- let interruptedBy: Fiber | null = null;
269
+ // Restore the original state of the work-in-progress
270
+ if (stashedWorkInProgressProperties === null) {
271
+ // This should never happen. Don't throw because this code is DEV-only.
272
+ warning(
273
+ false,
274
+ 'Could not replay rendering after an error. This is likely a bug in React. ' +
275
+ 'Please file an issue.',
276
+ );
277
+ return;
278
+ }
279
+ assignFiberPropertiesInDEV(
280
+ failedUnitOfWork,
281
+ stashedWorkInProgressProperties,
282
+ );
283
298
- let stashedWorkInProgressProperties;
299
- let replayUnitOfWork;
300
- let isReplayingFailedUnitOfWork;
301
- let originalReplayError;
302
- let rethrowOriginalError;
303
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
304
- stashedWorkInProgressProperties = null;
284
+ switch (failedUnitOfWork.tag) {
285
+ case HostRoot:
286
+ popHostContainer(failedUnitOfWork);
287
+ popTopLevelLegacyContextObject(failedUnitOfWork);
288
+ break;
289
+ case HostComponent:
290
+ popHostContext(failedUnitOfWork);
291
+ break;
292
+ case ClassComponent:
293
+ popLegacyContextProvider(failedUnitOfWork);
294
+ break;
295
+ case HostPortal:
296
+ popHostContainer(failedUnitOfWork);
297
+ break;
298
+ case ContextProvider:
299
+ popProvider(failedUnitOfWork);
300
+ break;
301
+ }
302
+ // Replay the begin phase.
303
+ isReplayingFailedUnitOfWork = true;
304
+ originalReplayError = thrownValue;
305
+ invokeGuardedCallback(null, workLoop, null, isAsync);
306
isReplayingFailedUnitOfWork = false;
307
originalReplayError = null;
307
- replayUnitOfWork = (
308
- failedUnitOfWork: Fiber,
309
- thrownValue: mixed,
310
- isAsync: boolean,
311
- ) => {
312
- if (
313
- thrownValue !== null &&
314
- typeof thrownValue === 'object' &&
315
- typeof thrownValue.then === 'function'
316
- ) {
317
- // Don't replay promises. Treat everything else like an error.
318
- // TODO: Need to figure out a different strategy if/when we add
319
- // support for catching other types.
320
- return;
321
- }
322
-
323
- // Restore the original state of the work-in-progress
324
- if (stashedWorkInProgressProperties === null) {
325
- // This should never happen. Don't throw because this code is DEV-only.
326
- warning(
327
- false,
328
- 'Could not replay rendering after an error. This is likely a bug in React. ' +
329
- 'Please file an issue.',
330
- );
331
- return;
332
- }
333
- assignFiberPropertiesInDEV(
334
- failedUnitOfWork,
335
- stashedWorkInProgressProperties,
336
- );
337
-
338
- switch (failedUnitOfWork.tag) {
339
- case HostRoot:
340
- popHostContainer(failedUnitOfWork);
341
- popTopLevelLegacyContextObject(failedUnitOfWork);
342
- break;
343
- case HostComponent:
344
- popHostContext(failedUnitOfWork);
345
- break;
346
- case ClassComponent:
347
- popLegacyContextProvider(failedUnitOfWork);
348
- break;
349
- case HostPortal:
350
- popHostContainer(failedUnitOfWork);
351
- break;
352
- case ContextProvider:
353
- popProvider(failedUnitOfWork);
354
- break;
355
- }
356
- // Replay the begin phase.
357
- isReplayingFailedUnitOfWork = true;
358
- originalReplayError = thrownValue;
359
- invokeGuardedCallback(null, workLoop, null, isAsync);
360
- isReplayingFailedUnitOfWork = false;
361
- originalReplayError = null;
362
- if (hasCaughtError()) {
363
- clearCaughtError();
308
+ if (hasCaughtError()) {
309
+ clearCaughtError();
310
365
- if (enableProfilerTimer) {
366
- // Stop "base" render timer again (after the re-thrown error).
367
- stopBaseRenderTimerIfRunning();
368
- }
369
- } else {
370
- // If the begin phase did not fail the second time, set this pointer
371
- // back to the original value.
372
- nextUnitOfWork = failedUnitOfWork;
373
- }
374
- };
375
- rethrowOriginalError = () => {
376
- throw originalReplayError;
377
- };
378
- }
379
-
380
- function resetStack() {
381
- if (nextUnitOfWork !== null) {
382
- let interruptedWork = nextUnitOfWork.return;
383
- while (interruptedWork !== null) {
384
- unwindInterruptedWork(interruptedWork);
385
- interruptedWork = interruptedWork.return;
311
+ if (enableProfilerTimer) {
312
+ // Stop "base" render timer again (after the re-thrown error).
313
+ stopBaseRenderTimerIfRunning();
314
}
315
+ } else {
316
+ // If the begin phase did not fail the second time, set this pointer
317
+ // back to the original value.
318
+ nextUnitOfWork = failedUnitOfWork;
319
}
320
+ };
321
+ rethrowOriginalError = () => {
322
+ throw originalReplayError;
323
+ };
324
+}
325
389
- if (__DEV__) {
390
- ReactStrictModeWarnings.discardPendingWarnings();
391
- stack.checkThatStackIsEmpty();
326
+function resetStack() {
327
+ if (nextUnitOfWork !== null) {
328
+ let interruptedWork = nextUnitOfWork.return;
329
+ while (interruptedWork !== null) {
330
+ unwindInterruptedWork(interruptedWork);
331
+ interruptedWork = interruptedWork.return;
332
}
333
+ }
334
394
- nextRoot = null;
395
- nextRenderExpirationTime = NoWork;
396
- nextLatestTimeoutMs = -1;
397
- nextRenderIsExpired = false;
398
- nextUnitOfWork = null;
399
-
400
- isRootReadyForCommit = false;
335
+ if (__DEV__) {
336
+ ReactStrictModeWarnings.discardPendingWarnings();
337
+ checkThatStackIsEmpty();
338
}
339
403
- function commitAllHostEffects() {
404
- while (nextEffect !== null) {
405
- if (__DEV__) {
406
- ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
407
- }
408
- recordEffect();
340
+ nextRoot = null;
341
+ nextRenderExpirationTime = NoWork;
342
+ nextLatestTimeoutMs = -1;
343
+ nextRenderIsExpired = false;
344
+ nextUnitOfWork = null;
345
410
- const effectTag = nextEffect.effectTag;
346
+ isRootReadyForCommit = false;
347
+}
348
412
- if (effectTag & ContentReset) {
413
- commitResetTextContent(nextEffect);
414
- }
349
+function commitAllHostEffects() {
350
+ while (nextEffect !== null) {
351
+ if (__DEV__) {
352
+ ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
353
+ }
354
+ recordEffect();
355
416
- if (effectTag & Ref) {
417
- const current = nextEffect.alternate;
418
- if (current !== null) {
419
- commitDetachRef(current);
420
- }
421
- }
356
+ const effectTag = nextEffect.effectTag;
357
423
- // The following switch statement is only concerned about placement,
424
- // updates, and deletions. To avoid needing to add a case for every
425
- // possible bitmap value, we remove the secondary effects from the
426
- // effect tag and switch on that value.
427
- let primaryEffectTag = effectTag & (Placement | Update | Deletion);
428
- switch (primaryEffectTag) {
429
- case Placement: {
430
- commitPlacement(nextEffect);
431
- // Clear the "placement" from effect tag so that we know that this is inserted, before
432
- // any life-cycles like componentDidMount gets called.
433
- // TODO: findDOMNode doesn't rely on this any more but isMounted
434
- // does and isMounted is deprecated anyway so we should be able
435
- // to kill this.
436
- nextEffect.effectTag &= ~Placement;
437
- break;
438
- }
439
- case PlacementAndUpdate: {
440
- // Placement
441
- commitPlacement(nextEffect);
442
- // Clear the "placement" from effect tag so that we know that this is inserted, before
443
- // any life-cycles like componentDidMount gets called.
444
- nextEffect.effectTag &= ~Placement;
445
-
446
- // Update
447
- const current = nextEffect.alternate;
448
- commitWork(current, nextEffect);
449
- break;
450
- }
451
- case Update: {
452
- const current = nextEffect.alternate;
453
- commitWork(current, nextEffect);
454
- break;
455
- }
456
- case Deletion: {
457
- commitDeletion(nextEffect);
458
- break;
459
- }
460
- }
461
- nextEffect = nextEffect.nextEffect;
358
+ if (effectTag & ContentReset) {
359
+ commitResetTextContent(nextEffect);
360
}
361
464
- if (__DEV__) {
465
- ReactDebugCurrentFiber.resetCurrentFiber();
362
+ if (effectTag & Ref) {
363
+ const current = nextEffect.alternate;
364
+ if (current !== null) {
365
+ commitDetachRef(current);
366
+ }
367
}
467
- }
368
469
- function commitBeforeMutationLifecycles() {
470
- while (nextEffect !== null) {
471
- const effectTag = nextEffect.effectTag;
472
-
473
- if (effectTag & Snapshot) {
474
- recordEffect();
369
+ // The following switch statement is only concerned about placement,
370
+ // updates, and deletions. To avoid needing to add a case for every
371
+ // possible bitmap value, we remove the secondary effects from the
372
+ // effect tag and switch on that value.
373
+ let primaryEffectTag = effectTag & (Placement | Update | Deletion);
374
+ switch (primaryEffectTag) {
375
+ case Placement: {
376
+ commitPlacement(nextEffect);
377
+ // Clear the "placement" from effect tag so that we know that this is inserted, before
378
+ // any life-cycles like componentDidMount gets called.
379
+ // TODO: findDOMNode doesn't rely on this any more but isMounted
380
+ // does and isMounted is deprecated anyway so we should be able
381
+ // to kill this.
382
+ nextEffect.effectTag &= ~Placement;
383
+ break;
384
+ }
385
+ case PlacementAndUpdate: {
386
+ // Placement
387
+ commitPlacement(nextEffect);
388
+ // Clear the "placement" from effect tag so that we know that this is inserted, before
389
+ // any life-cycles like componentDidMount gets called.
390
+ nextEffect.effectTag &= ~Placement;
391
+
392
+ // Update
393
const current = nextEffect.alternate;
476
- commitBeforeMutationLifeCycles(current, nextEffect);
394
+ commitWork(current, nextEffect);
395
+ break;
396
+ }
397
+ case Update: {
398
+ const current = nextEffect.alternate;
399
+ commitWork(current, nextEffect);
400
+ break;
401
+ }
402
+ case Deletion: {
403
+ commitDeletion(nextEffect);
404
+ break;
405
}
478
-
479
- // Don't cleanup effects yet;
480
- // This will be done by commitAllLifeCycles()
481
- nextEffect = nextEffect.nextEffect;
406
}
407
+ nextEffect = nextEffect.nextEffect;
408
}
409
485
- function commitAllLifeCycles(
486
- finishedRoot: FiberRoot,
487
- currentTime: ExpirationTime,
488
- committedExpirationTime: ExpirationTime,
489
- ) {
490
- if (__DEV__) {
491
- ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
410
+ if (__DEV__) {
411
+ ReactDebugCurrentFiber.resetCurrentFiber();
412
+ }
413
+}
414
493
- if (warnAboutDeprecatedLifecycles) {
494
- ReactStrictModeWarnings.flushPendingDeprecationWarnings();
495
- }
415
+function commitBeforeMutationLifecycles() {
416
+ while (nextEffect !== null) {
417
+ const effectTag = nextEffect.effectTag;
418
+
419
+ if (effectTag & Snapshot) {
420
+ recordEffect();
421
+ const current = nextEffect.alternate;
422
+ commitBeforeMutationLifeCycles(current, nextEffect);
423
}
497
- while (nextEffect !== null) {
498
- const effectTag = nextEffect.effectTag;
424
500
- if (effectTag & (Update | Callback)) {
501
- recordEffect();
502
- const current = nextEffect.alternate;
503
- commitLifeCycles(
504
- finishedRoot,
505
- current,
506
- nextEffect,
507
- currentTime,
508
- committedExpirationTime,
509
- );
510
- }
425
+ // Don't cleanup effects yet;
426
+ // This will be done by commitAllLifeCycles()
427
+ nextEffect = nextEffect.nextEffect;
428
+ }
429
+}
430
512
- if (effectTag & Ref) {
513
- recordEffect();
514
- commitAttachRef(nextEffect);
515
- }
431
+function commitAllLifeCycles(
432
+ finishedRoot: FiberRoot,
433
+ currentTime: ExpirationTime,
434
+ committedExpirationTime: ExpirationTime,
435
+) {
436
+ if (__DEV__) {
437
+ ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
438
517
- const next = nextEffect.nextEffect;
518
- // Ensure that we clean these up so that we don't accidentally keep them.
519
- // I'm not actually sure this matters because we can't reset firstEffect
520
- // and lastEffect since they're on every node, not just the effectful
521
- // ones. So we have to clean everything as we reuse nodes anyway.
522
- nextEffect.nextEffect = null;
523
- // Ensure that we reset the effectTag here so that we can rely on effect
524
- // tags to reason about the current life-cycle.
525
- nextEffect = next;
439
+ if (warnAboutDeprecatedLifecycles) {
440
+ ReactStrictModeWarnings.flushPendingDeprecationWarnings();
441
}
442
}
443
+ while (nextEffect !== null) {
444
+ const effectTag = nextEffect.effectTag;
445
529
- function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
530
- return (
531
- legacyErrorBoundariesThatAlreadyFailed !== null &&
532
- legacyErrorBoundariesThatAlreadyFailed.has(instance)
533
- );
534
- }
535
-
536
- function markLegacyErrorBoundaryAsFailed(instance: mixed) {
537
- if (legacyErrorBoundariesThatAlreadyFailed === null) {
538
- legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
539
- } else {
540
- legacyErrorBoundariesThatAlreadyFailed.add(instance);
446
+ if (effectTag & (Update | Callback)) {
447
+ recordEffect();
448
+ const current = nextEffect.alternate;
449
+ commitLifeCycles(
450
+ finishedRoot,
451
+ current,
452
+ nextEffect,
453
+ currentTime,
454
+ committedExpirationTime,
455
+ );
456
}
457
+
458
+ if (effectTag & Ref) {
459
+ recordEffect();
460
+ commitAttachRef(nextEffect);
461
+ }
462
+
463
+ const next = nextEffect.nextEffect;
464
+ // Ensure that we clean these up so that we don't accidentally keep them.
465
+ // I'm not actually sure this matters because we can't reset firstEffect
466
+ // and lastEffect since they're on every node, not just the effectful
467
+ // ones. So we have to clean everything as we reuse nodes anyway.
468
+ nextEffect.nextEffect = null;
469
+ // Ensure that we reset the effectTag here so that we can rely on effect
470
+ // tags to reason about the current life-cycle.
471
+ nextEffect = next;
472
}
473
+}
474
+
475
+function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
476
+ return (
477
+ legacyErrorBoundariesThatAlreadyFailed !== null &&
478
+ legacyErrorBoundariesThatAlreadyFailed.has(instance)
479
+ );
480
+}
481
544
- function commitRoot(finishedWork: Fiber): ExpirationTime {
545
- isWorking = true;
546
- isCommitting = true;
547
- startCommitTimer();
482
+function markLegacyErrorBoundaryAsFailed(instance: mixed) {
483
+ if (legacyErrorBoundariesThatAlreadyFailed === null) {
484
+ legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
485
+ } else {
486
+ legacyErrorBoundariesThatAlreadyFailed.add(instance);
487
+ }
488
+}
489
549
- const root: FiberRoot = finishedWork.stateNode;
550
- invariant(
551
- root.current !== finishedWork,
552
- 'Cannot commit the same tree as before. This is probably a bug ' +
553
- 'related to the return field. This error is likely caused by a bug ' +
554
- 'in React. Please file an issue.',
555
- );
556
- const committedExpirationTime = root.pendingCommitExpirationTime;
557
- invariant(
558
- committedExpirationTime !== NoWork,
559
- 'Cannot commit an incomplete root. This error is likely caused by a ' +
560
- 'bug in React. Please file an issue.',
561
- );
562
- root.pendingCommitExpirationTime = NoWork;
490
+function commitRoot(finishedWork: Fiber): ExpirationTime {
491
+ isWorking = true;
492
+ isCommitting = true;
493
+ startCommitTimer();
494
+
495
+ const root: FiberRoot = finishedWork.stateNode;
496
+ invariant(
497
+ root.current !== finishedWork,
498
+ 'Cannot commit the same tree as before. This is probably a bug ' +
499
+ 'related to the return field. This error is likely caused by a bug ' +
500
+ 'in React. Please file an issue.',
501
+ );
502
+ const committedExpirationTime = root.pendingCommitExpirationTime;
503
+ invariant(
504
+ committedExpirationTime !== NoWork,
505
+ 'Cannot commit an incomplete root. This error is likely caused by a ' +
506
+ 'bug in React. Please file an issue.',
507
+ );
508
+ root.pendingCommitExpirationTime = NoWork;
509
564
- const currentTime = recalculateCurrentTime();
510
+ const currentTime = recalculateCurrentTime();
511
566
- // Reset this to null before calling lifecycles
567
- ReactCurrentOwner.current = null;
512
+ // Reset this to null before calling lifecycles
513
+ ReactCurrentOwner.current = null;
514
569
- let firstEffect;
570
- if (finishedWork.effectTag > PerformedWork) {
571
- // A fiber's effect list consists only of its children, not itself. So if
572
- // the root has an effect, we need to add it to the end of the list. The
573
- // resulting list is the set that would belong to the root's parent, if
574
- // it had one; that is, all the effects in the tree including the root.
575
- if (finishedWork.lastEffect !== null) {
576
- finishedWork.lastEffect.nextEffect = finishedWork;
577
- firstEffect = finishedWork.firstEffect;
578
- } else {
579
- firstEffect = finishedWork;
580
- }
581
- } else {
582
- // There is no effect on the root.
515
+ let firstEffect;
516
+ if (finishedWork.effectTag > PerformedWork) {
517
+ // A fiber's effect list consists only of its children, not itself. So if
518
+ // the root has an effect, we need to add it to the end of the list. The
519
+ // resulting list is the set that would belong to the root's parent, if
520
+ // it had one; that is, all the effects in the tree including the root.
521
+ if (finishedWork.lastEffect !== null) {
522
+ finishedWork.lastEffect.nextEffect = finishedWork;
523
firstEffect = finishedWork.firstEffect;
524
+ } else {
525
+ firstEffect = finishedWork;
526
}
527
+ } else {
528
+ // There is no effect on the root.
529
+ firstEffect = finishedWork.firstEffect;
530
+ }
531
586
- prepareForCommit(root.containerInfo);
532
+ prepareForCommit(root.containerInfo);
533
588
- // Invoke instances of getSnapshotBeforeUpdate before mutation.
589
- nextEffect = firstEffect;
590
- startCommitSnapshotEffectsTimer();
591
- while (nextEffect !== null) {
592
- let didError = false;
593
- let error;
594
- if (__DEV__) {
595
- invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
596
- if (hasCaughtError()) {
597
- didError = true;
598
- error = clearCaughtError();
599
- }
600
- } else {
601
- try {
602
- commitBeforeMutationLifecycles();
603
- } catch (e) {
604
- didError = true;
605
- error = e;
606
- }
534
+ // Invoke instances of getSnapshotBeforeUpdate before mutation.
535
+ nextEffect = firstEffect;
536
+ startCommitSnapshotEffectsTimer();
537
+ while (nextEffect !== null) {
538
+ let didError = false;
539
+ let error;
540
+ if (__DEV__) {
541
+ invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
542
+ if (hasCaughtError()) {
543
+ didError = true;
544
+ error = clearCaughtError();
545
}
608
- if (didError) {
609
- invariant(
610
- nextEffect !== null,
611
- 'Should have next effect. This error is likely caused by a bug ' +
612
- 'in React. Please file an issue.',
613
- );
614
- onCommitPhaseError(nextEffect, error);
615
- // Clean-up
616
- if (nextEffect !== null) {
617
- nextEffect = nextEffect.nextEffect;
618
- }
546
+ } else {
547
+ try {
548
+ commitBeforeMutationLifecycles();
549
+ } catch (e) {
550
+ didError = true;
551
+ error = e;
552
}
553
}
621
- stopCommitSnapshotEffectsTimer();
622
-
623
- // Commit all the side-effects within a tree. We'll do this in two passes.
624
- // The first pass performs all the host insertions, updates, deletions and
625
- // ref unmounts.
626
- nextEffect = firstEffect;
627
- startCommitHostEffectsTimer();
628
- while (nextEffect !== null) {
629
- let didError = false;
630
- let error;
631
- if (__DEV__) {
632
- invokeGuardedCallback(null, commitAllHostEffects, null);
633
- if (hasCaughtError()) {
634
- didError = true;
635
- error = clearCaughtError();
636
- }
637
- } else {
638
- try {
639
- commitAllHostEffects();
640
- } catch (e) {
641
- didError = true;
642
- error = e;
643
- }
644
- }
645
- if (didError) {
646
- invariant(
647
- nextEffect !== null,
648
- 'Should have next effect. This error is likely caused by a bug ' +
649
- 'in React. Please file an issue.',
650
- );
651
- onCommitPhaseError(nextEffect, error);
652
- // Clean-up
653
- if (nextEffect !== null) {
654
- nextEffect = nextEffect.nextEffect;
655
- }
554
+ if (didError) {
555
+ invariant(
556
+ nextEffect !== null,
557
+ 'Should have next effect. This error is likely caused by a bug ' +
558
+ 'in React. Please file an issue.',
559
+ );
560
+ captureCommitPhaseError(nextEffect, error);
561
+ // Clean-up
562
+ if (nextEffect !== null) {
563
+ nextEffect = nextEffect.nextEffect;
564
}
565
}
658
- stopCommitHostEffectsTimer();
659
-
660
- resetAfterCommit(root.containerInfo);
661
-
662
- // The work-in-progress tree is now the current tree. This must come after
663
- // the first pass of the commit phase, so that the previous tree is still
664
- // current during componentWillUnmount, but before the second pass, so that
665
- // the finished work is current during componentDidMount/Update.
666
- root.current = finishedWork;
667
-
668
- // In the second pass we'll perform all life-cycles and ref callbacks.
669
- // Life-cycles happen as a separate pass so that all placements, updates,
670
- // and deletions in the entire tree have already been invoked.
671
- // This pass also triggers any renderer-specific initial effects.
672
- nextEffect = firstEffect;
673
- startCommitLifeCyclesTimer();
674
- while (nextEffect !== null) {
675
- let didError = false;
676
- let error;
677
- if (__DEV__) {
678
- invokeGuardedCallback(
679
- null,
680
- commitAllLifeCycles,
681
- null,
682
- root,
683
- currentTime,
684
- committedExpirationTime,
685
- );
686
- if (hasCaughtError()) {
687
- didError = true;
688
- error = clearCaughtError();
689
- }
690
- } else {
691
- try {
692
- commitAllLifeCycles(root, currentTime, committedExpirationTime);
693
- } catch (e) {
694
- didError = true;
695
- error = e;
696
- }
566
+ }
567
+ stopCommitSnapshotEffectsTimer();
568
+
569
+ // Commit all the side-effects within a tree. We'll do this in two passes.
570
+ // The first pass performs all the host insertions, updates, deletions and
571
+ // ref unmounts.
572
+ nextEffect = firstEffect;
573
+ startCommitHostEffectsTimer();
574
+ while (nextEffect !== null) {
575
+ let didError = false;
576
+ let error;
577
+ if (__DEV__) {
578
+ invokeGuardedCallback(null, commitAllHostEffects, null);
579
+ if (hasCaughtError()) {
580
+ didError = true;
581
+ error = clearCaughtError();
582
}
698
- if (didError) {
699
- invariant(
700
- nextEffect !== null,
701
- 'Should have next effect. This error is likely caused by a bug ' +
702
- 'in React. Please file an issue.',
703
- );
704
- onCommitPhaseError(nextEffect, error);
705
- if (nextEffect !== null) {
706
- nextEffect = nextEffect.nextEffect;
707
- }
583
+ } else {
584
+ try {
585
+ commitAllHostEffects();
586
+ } catch (e) {
587
+ didError = true;
588
+ error = e;
589
}
590
}
710
-
711
- if (enableProfilerTimer) {
712
- if (__DEV__) {
713
- checkActualRenderTimeStackEmpty();
591
+ if (didError) {
592
+ invariant(
593
+ nextEffect !== null,
594
+ 'Should have next effect. This error is likely caused by a bug ' +
595
+ 'in React. Please file an issue.',
596
+ );
597
+ captureCommitPhaseError(nextEffect, error);
598
+ // Clean-up
599
+ if (nextEffect !== null) {
600
+ nextEffect = nextEffect.nextEffect;
601
}
715
- resetActualRenderTimer();
602
}
717
-
718
- isCommitting = false;
719
- isWorking = false;
720
- stopCommitLifeCyclesTimer();
721
- stopCommitTimer();
722
- if (typeof onCommitRoot === 'function') {
723
- onCommitRoot(finishedWork.stateNode);
603
+ }
604
+ stopCommitHostEffectsTimer();
605
+
606
+ resetAfterCommit(root.containerInfo);
607
+
608
+ // The work-in-progress tree is now the current tree. This must come after
609
+ // the first pass of the commit phase, so that the previous tree is still
610
+ // current during componentWillUnmount, but before the second pass, so that
611
+ // the finished work is current during componentDidMount/Update.
612
+ root.current = finishedWork;
613
+
614
+ // In the second pass we'll perform all life-cycles and ref callbacks.
615
+ // Life-cycles happen as a separate pass so that all placements, updates,
616
+ // and deletions in the entire tree have already been invoked.
617
+ // This pass also triggers any renderer-specific initial effects.
618
+ nextEffect = firstEffect;
619
+ startCommitLifeCyclesTimer();
620
+ while (nextEffect !== null) {
621
+ let didError = false;
622
+ let error;
623
+ if (__DEV__) {
624
+ invokeGuardedCallback(
625
+ null,
626
+ commitAllLifeCycles,
627
+ null,
628
+ root,
629
+ currentTime,
630
+ committedExpirationTime,
631
+ );
632
+ if (hasCaughtError()) {
633
+ didError = true;
634
+ error = clearCaughtError();
635
+ }
636
+ } else {
637
+ try {
638
+ commitAllLifeCycles(root, currentTime, committedExpirationTime);
639
+ } catch (e) {
640
+ didError = true;
641
+ error = e;
642
+ }
643
}
725
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
726
- ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
644
+ if (didError) {
645
+ invariant(
646
+ nextEffect !== null,
647
+ 'Should have next effect. This error is likely caused by a bug ' +
648
+ 'in React. Please file an issue.',
649
+ );
650
+ captureCommitPhaseError(nextEffect, error);
651
+ if (nextEffect !== null) {
652
+ nextEffect = nextEffect.nextEffect;
653
+ }
654
}
655
+ }
656
729
- markCommittedPriorityLevels(root, currentTime, root.current.expirationTime);
730
- const remainingTime = findNextPendingPriorityLevel(root);
731
- if (remainingTime === NoWork) {
732
- // If there's no remaining work, we can clear the set of already failed
733
- // error boundaries.
734
- legacyErrorBoundariesThatAlreadyFailed = null;
657
+ if (enableProfilerTimer) {
658
+ if (__DEV__) {
659
+ checkActualRenderTimeStackEmpty();
660
}
736
- return remainingTime;
661
+ resetActualRenderTimer();
662
}
663
739
- function resetExpirationTime(
740
- workInProgress: Fiber,
741
- renderTime: ExpirationTime,
742
- ) {
743
- if (renderTime !== Never && workInProgress.expirationTime === Never) {
744
- // The children of this component are hidden. Don't bubble their
745
- // expiration times.
746
- return;
747
- }
664
+ isCommitting = false;
665
+ isWorking = false;
666
+ stopCommitLifeCyclesTimer();
667
+ stopCommitTimer();
668
+ if (typeof onCommitRoot === 'function') {
669
+ onCommitRoot(finishedWork.stateNode);
670
+ }
671
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
672
+ ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
673
+ }
674
749
- // Check for pending updates.
750
- let newExpirationTime = NoWork;
751
- switch (workInProgress.tag) {
752
- case HostRoot:
753
- case ClassComponent: {
754
- const updateQueue = workInProgress.updateQueue;
755
- if (updateQueue !== null) {
756
- newExpirationTime = updateQueue.expirationTime;
757
- }
675
+ markCommittedPriorityLevels(root, currentTime, root.current.expirationTime);
676
+ const remainingTime = findNextPendingPriorityLevel(root);
677
+ if (remainingTime === NoWork) {
678
+ // If there's no remaining work, we can clear the set of already failed
679
+ // error boundaries.
680
+ legacyErrorBoundariesThatAlreadyFailed = null;
681
+ }
682
+ return remainingTime;
683
+}
684
+
685
+function resetExpirationTime(
686
+ workInProgress: Fiber,
687
+ renderTime: ExpirationTime,
688
+) {
689
+ if (renderTime !== Never && workInProgress.expirationTime === Never) {
690
+ // The children of this component are hidden. Don't bubble their
691
+ // expiration times.
692
+ return;
693
+ }
694
+
695
+ // Check for pending updates.
696
+ let newExpirationTime = NoWork;
697
+ switch (workInProgress.tag) {
698
+ case HostRoot:
699
+ case ClassComponent: {
700
+ const updateQueue = workInProgress.updateQueue;
701
+ if (updateQueue !== null) {
702
+ newExpirationTime = updateQueue.expirationTime;
703
}
704
}
705
+ }
706
761
- // TODO: Calls need to visit stateNode
707
+ // TODO: Calls need to visit stateNode
708
763
- // Bubble up the earliest expiration time.
764
- // (And "base" render timers if that feature flag is enabled)
765
- if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
766
- let treeBaseTime = workInProgress.selfBaseTime;
767
- let child = workInProgress.child;
768
- while (child !== null) {
769
- treeBaseTime += child.treeBaseTime;
770
- if (
771
- child.expirationTime !== NoWork &&
772
- (newExpirationTime === NoWork ||
773
- newExpirationTime > child.expirationTime)
774
- ) {
775
- newExpirationTime = child.expirationTime;
776
- }
777
- child = child.sibling;
709
+ // Bubble up the earliest expiration time.
710
+ // (And "base" render timers if that feature flag is enabled)
711
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
712
+ let treeBaseTime = workInProgress.selfBaseTime;
713
+ let child = workInProgress.child;
714
+ while (child !== null) {
715
+ treeBaseTime += child.treeBaseTime;
716
+ if (
717
+ child.expirationTime !== NoWork &&
718
+ (newExpirationTime === NoWork ||
719
+ newExpirationTime > child.expirationTime)
720
+ ) {
721
+ newExpirationTime = child.expirationTime;
722
}
779
- workInProgress.treeBaseTime = treeBaseTime;
780
- } else {
781
- let child = workInProgress.child;
782
- while (child !== null) {
783
- if (
784
- child.expirationTime !== NoWork &&
785
- (newExpirationTime === NoWork ||
786
- newExpirationTime > child.expirationTime)
787
- ) {
788
- newExpirationTime = child.expirationTime;
789
- }
790
- child = child.sibling;
723
+ child = child.sibling;
724
+ }
725
+ workInProgress.treeBaseTime = treeBaseTime;
726
+ } else {
727
+ let child = workInProgress.child;
728
+ while (child !== null) {
729
+ if (
730
+ child.expirationTime !== NoWork &&
731
+ (newExpirationTime === NoWork ||
732
+ newExpirationTime > child.expirationTime)
733
+ ) {
734
+ newExpirationTime = child.expirationTime;
735
}
736
+ child = child.sibling;
737
}
793
-
794
- workInProgress.expirationTime = newExpirationTime;
738
}
739
797
- function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
798
- // Attempt to complete the current unit of work, then move to the
799
- // next sibling. If there are no more siblings, return to the
800
- // parent fiber.
801
- while (true) {
802
- // The current, flushed, state of this fiber is the alternate.
803
- // Ideally nothing should rely on this, but relying on it here
804
- // means that we don't need an additional field on the work in
805
- // progress.
806
- const current = workInProgress.alternate;
740
+ workInProgress.expirationTime = newExpirationTime;
741
+}
742
+
743
+function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
744
+ // Attempt to complete the current unit of work, then move to the
745
+ // next sibling. If there are no more siblings, return to the
746
+ // parent fiber.
747
+ while (true) {
748
+ // The current, flushed, state of this fiber is the alternate.
749
+ // Ideally nothing should rely on this, but relying on it here
750
+ // means that we don't need an additional field on the work in
751
+ // progress.
752
+ const current = workInProgress.alternate;
753
+ if (__DEV__) {
754
+ ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
755
+ }
756
+
757
+ const returnFiber = workInProgress.return;
758
+ const siblingFiber = workInProgress.sibling;
759
+
760
+ if ((workInProgress.effectTag & Incomplete) === NoEffect) {
761
+ // This fiber completed.
762
+ let next = completeWork(
763
+ current,
764
+ workInProgress,
765
+ nextRenderExpirationTime,
766
+ );
767
+ stopWorkTimer(workInProgress);
768
+ resetExpirationTime(workInProgress, nextRenderExpirationTime);
769
if (__DEV__) {
808
- ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
770
+ ReactDebugCurrentFiber.resetCurrentFiber();
771
}
772
811
- const returnFiber = workInProgress.return;
812
- const siblingFiber = workInProgress.sibling;
813
-
814
- if ((workInProgress.effectTag & Incomplete) === NoEffect) {
815
- // This fiber completed.
816
- let next = completeWork(
817
- current,
818
- workInProgress,
819
- nextRenderExpirationTime,
820
- );
773
+ if (next !== null) {
774
stopWorkTimer(workInProgress);
822
- resetExpirationTime(workInProgress, nextRenderExpirationTime);
823
- if (__DEV__) {
824
- ReactDebugCurrentFiber.resetCurrentFiber();
775
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
776
+ ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
777
}
778
+ // If completing this work spawned new work, do that next. We'll come
779
+ // back here again.
780
+ return next;
781
+ }
782
827
- if (next !== null) {
828
- stopWorkTimer(workInProgress);
829
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
830
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
831
- }
832
- // If completing this work spawned new work, do that next. We'll come
833
- // back here again.
834
- return next;
783
+ if (
784
+ returnFiber !== null &&
785
+ // Do not append effects to parents if a sibling failed to complete
786
+ (returnFiber.effectTag & Incomplete) === NoEffect
787
+ ) {
788
+ // Append all the effects of the subtree and this fiber onto the effect
789
+ // list of the parent. The completion order of the children affects the
790
+ // side-effect order.
791
+ if (returnFiber.firstEffect === null) {
792
+ returnFiber.firstEffect = workInProgress.firstEffect;
793
}
836
-
837
- if (
838
- returnFiber !== null &&
839
- // Do not append effects to parents if a sibling failed to complete
840
- (returnFiber.effectTag & Incomplete) === NoEffect
841
- ) {
842
- // Append all the effects of the subtree and this fiber onto the effect
843
- // list of the parent. The completion order of the children affects the
844
- // side-effect order.
845
- if (returnFiber.firstEffect === null) {
846
- returnFiber.firstEffect = workInProgress.firstEffect;
847
- }
848
- if (workInProgress.lastEffect !== null) {
849
- if (returnFiber.lastEffect !== null) {
850
- returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
851
- }
852
- returnFiber.lastEffect = workInProgress.lastEffect;
794
+ if (workInProgress.lastEffect !== null) {
795
+ if (returnFiber.lastEffect !== null) {
796
+ returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
797
}
798
+ returnFiber.lastEffect = workInProgress.lastEffect;
799
+ }
800
855
- // If this fiber had side-effects, we append it AFTER the children's
856
- // side-effects. We can perform certain side-effects earlier if
857
- // needed, by doing multiple passes over the effect list. We don't want
858
- // to schedule our own side-effect on our own list because if end up
859
- // reusing children we'll schedule this effect onto itself since we're
860
- // at the end.
861
- const effectTag = workInProgress.effectTag;
862
- // Skip both NoWork and PerformedWork tags when creating the effect list.
863
- // PerformedWork effect is read by React DevTools but shouldn't be committed.
864
- if (effectTag > PerformedWork) {
865
- if (returnFiber.lastEffect !== null) {
866
- returnFiber.lastEffect.nextEffect = workInProgress;
867
- } else {
868
- returnFiber.firstEffect = workInProgress;
869
- }
870
- returnFiber.lastEffect = workInProgress;
801
+ // If this fiber had side-effects, we append it AFTER the children's
802
+ // side-effects. We can perform certain side-effects earlier if
803
+ // needed, by doing multiple passes over the effect list. We don't want
804
+ // to schedule our own side-effect on our own list because if end up
805
+ // reusing children we'll schedule this effect onto itself since we're
806
+ // at the end.
807
+ const effectTag = workInProgress.effectTag;
808
+ // Skip both NoWork and PerformedWork tags when creating the effect list.
809
+ // PerformedWork effect is read by React DevTools but shouldn't be committed.
810
+ if (effectTag > PerformedWork) {
811
+ if (returnFiber.lastEffect !== null) {
812
+ returnFiber.lastEffect.nextEffect = workInProgress;
813
+ } else {
814
+ returnFiber.firstEffect = workInProgress;
815
}
816
+ returnFiber.lastEffect = workInProgress;
817
}
818
+ }
819
874
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
875
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
876
- }
820
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
821
+ ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
822
+ }
823
878
- if (siblingFiber !== null) {
879
- // If there is more work to do in this returnFiber, do that next.
880
- return siblingFiber;
881
- } else if (returnFiber !== null) {
882
- // If there's no more work in this returnFiber. Complete the returnFiber.
883
- workInProgress = returnFiber;
884
- continue;
885
- } else {
886
- // We've reached the root.
887
- isRootReadyForCommit = true;
888
- return null;
889
- }
824
+ if (siblingFiber !== null) {
825
+ // If there is more work to do in this returnFiber, do that next.
826
+ return siblingFiber;
827
+ } else if (returnFiber !== null) {
828
+ // If there's no more work in this returnFiber. Complete the returnFiber.
829
+ workInProgress = returnFiber;
830
+ continue;
831
} else {
891
- // This fiber did not complete because something threw. Pop values off
892
- // the stack without entering the complete phase. If this is a boundary,
893
- // capture values if possible.
894
- const next = unwindWork(
895
- workInProgress,
896
- nextRenderIsExpired,
897
- nextRenderExpirationTime,
898
- );
899
- // Because this fiber did not complete, don't reset its expiration time.
900
- if (workInProgress.effectTag & DidCapture) {
901
- // Restarting an error boundary
902
- stopFailedWorkTimer(workInProgress);
903
- } else {
904
- stopWorkTimer(workInProgress);
905
- }
906
-
907
- if (__DEV__) {
908
- ReactDebugCurrentFiber.resetCurrentFiber();
909
- }
910
-
911
- if (next !== null) {
912
- stopWorkTimer(workInProgress);
913
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
914
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
915
- }
916
- // If completing this work spawned new work, do that next. We'll come
917
- // back here again.
918
- // Since we're restarting, remove anything that is not a host effect
919
- // from the effect tag.
920
- next.effectTag &= HostEffectMask;
921
- return next;
922
- }
832
+ // We've reached the root.
833
+ isRootReadyForCommit = true;
834
+ return null;
835
+ }
836
+ } else {
837
+ // This fiber did not complete because something threw. Pop values off
838
+ // the stack without entering the complete phase. If this is a boundary,
839
+ // capture values if possible.
840
+ const next = unwindWork(
841
+ workInProgress,
842
+ nextRenderIsExpired,
843
+ nextRenderExpirationTime,
844
+ );
845
+ // Because this fiber did not complete, don't reset its expiration time.
846
+ if (workInProgress.effectTag & DidCapture) {
847
+ // Restarting an error boundary
848
+ stopFailedWorkTimer(workInProgress);
849
+ } else {
850
+ stopWorkTimer(workInProgress);
851
+ }
852
924
- if (returnFiber !== null) {
925
- // Mark the parent fiber as incomplete and clear its effect list.
926
- returnFiber.firstEffect = returnFiber.lastEffect = null;
927
- returnFiber.effectTag |= Incomplete;
928
- }
853
+ if (__DEV__) {
854
+ ReactDebugCurrentFiber.resetCurrentFiber();
855
+ }
856
857
+ if (next !== null) {
858
+ stopWorkTimer(workInProgress);
859
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
860
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
861
}
933
-
934
- if (siblingFiber !== null) {
935
- // If there is more work to do in this returnFiber, do that next.
936
- return siblingFiber;
937
- } else if (returnFiber !== null) {
938
- // If there's no more work in this returnFiber. Complete the returnFiber.
939
- workInProgress = returnFiber;
940
- continue;
941
- } else {
942
- return null;
943
- }
862
+ // If completing this work spawned new work, do that next. We'll come
863
+ // back here again.
864
+ // Since we're restarting, remove anything that is not a host effect
865
+ // from the effect tag.
866
+ next.effectTag &= HostEffectMask;
867
+ return next;
868
}
945
- }
869
947
- // Without this explicit null return Flow complains of invalid return type
948
- // TODO Remove the above while(true) loop
949
- // eslint-disable-next-line no-unreachable
950
- return null;
951
- }
870
+ if (returnFiber !== null) {
871
+ // Mark the parent fiber as incomplete and clear its effect list.
872
+ returnFiber.firstEffect = returnFiber.lastEffect = null;
873
+ returnFiber.effectTag |= Incomplete;
874
+ }
875
953
- function performUnitOfWork(workInProgress: Fiber): Fiber | null {
954
- // The current, flushed, state of this fiber is the alternate.
955
- // Ideally nothing should rely on this, but relying on it here
956
- // means that we don't need an additional field on the work in
957
- // progress.
958
- const current = workInProgress.alternate;
876
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
877
+ ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
878
+ }
879
960
- // See if beginning this work spawns more work.
961
- startWorkTimer(workInProgress);
962
- if (__DEV__) {
963
- ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
880
+ if (siblingFiber !== null) {
881
+ // If there is more work to do in this returnFiber, do that next.
882
+ return siblingFiber;
883
+ } else if (returnFiber !== null) {
884
+ // If there's no more work in this returnFiber. Complete the returnFiber.
885
+ workInProgress = returnFiber;
886
+ continue;
887
+ } else {
888
+ return null;
889
+ }
890
}
891
+ }
892
966
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
967
- stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
968
- stashedWorkInProgressProperties,
969
- workInProgress,
970
- );
971
- }
893
+ // Without this explicit null return Flow complains of invalid return type
894
+ // TODO Remove the above while(true) loop
895
+ // eslint-disable-next-line no-unreachable
896
+ return null;
897
+}
898
973
- let next;
974
- if (enableProfilerTimer) {
975
- if (workInProgress.mode & ProfileMode) {
976
- startBaseRenderTimer();
977
- }
899
+function performUnitOfWork(workInProgress: Fiber): Fiber | null {
900
+ // The current, flushed, state of this fiber is the alternate.
901
+ // Ideally nothing should rely on this, but relying on it here
902
+ // means that we don't need an additional field on the work in
903
+ // progress.
904
+ const current = workInProgress.alternate;
905
+
906
+ // See if beginning this work spawns more work.
907
+ startWorkTimer(workInProgress);
908
+ if (__DEV__) {
909
+ ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
910
+ }
911
979
- next = beginWork(current, workInProgress, nextRenderExpirationTime);
912
+ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
913
+ stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
914
+ stashedWorkInProgressProperties,
915
+ workInProgress,
916
+ );
917
+ }
918
981
- if (workInProgress.mode & ProfileMode) {
982
- // Update "base" time if the render wasn't bailed out on.
983
- recordElapsedBaseRenderTimeIfRunning(workInProgress);
984
- stopBaseRenderTimerIfRunning();
985
- }
986
- } else {
987
- next = beginWork(current, workInProgress, nextRenderExpirationTime);
919
+ let next;
920
+ if (enableProfilerTimer) {
921
+ if (workInProgress.mode & ProfileMode) {
922
+ startBaseRenderTimer();
923
}
924
990
- if (__DEV__) {
991
- ReactDebugCurrentFiber.resetCurrentFiber();
992
- if (isReplayingFailedUnitOfWork) {
993
- // Currently replaying a failed unit of work. This should be unreachable,
994
- // because the render phase is meant to be idempotent, and it should
995
- // have thrown again. Since it didn't, rethrow the original error, so
996
- // React's internal stack is not misaligned.
997
- rethrowOriginalError();
998
- }
999
- }
1000
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
1001
- ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
1002
- }
925
+ next = beginWork(current, workInProgress, nextRenderExpirationTime);
926
1004
- if (next === null) {
1005
- // If this doesn't spawn new work, complete the current work.
1006
- next = completeUnitOfWork(workInProgress);
927
+ if (workInProgress.mode & ProfileMode) {
928
+ // Update "base" time if the render wasn't bailed out on.
929
+ recordElapsedBaseRenderTimeIfRunning(workInProgress);
930
+ stopBaseRenderTimerIfRunning();
931
}
932
+ } else {
933
+ next = beginWork(current, workInProgress, nextRenderExpirationTime);
934
+ }
935
1009
- ReactCurrentOwner.current = null;
936
+ if (__DEV__) {
937
+ ReactDebugCurrentFiber.resetCurrentFiber();
938
+ if (isReplayingFailedUnitOfWork) {
939
+ // Currently replaying a failed unit of work. This should be unreachable,
940
+ // because the render phase is meant to be idempotent, and it should
941
+ // have thrown again. Since it didn't, rethrow the original error, so
942
+ // React's internal stack is not misaligned.
943
+ rethrowOriginalError();
944
+ }
945
+ }
946
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
947
+ ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
948
+ }
949
1011
- return next;
950
+ if (next === null) {
951
+ // If this doesn't spawn new work, complete the current work.
952
+ next = completeUnitOfWork(workInProgress);
953
}
954
1014
- function workLoop(isAsync) {
1015
- if (!isAsync) {
1016
- // Flush all expired work.
1017
- while (nextUnitOfWork !== null) {
1018
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1019
- }
1020
- } else {
1021
- // Flush asynchronous work until the deadline runs out of time.
1022
- while (nextUnitOfWork !== null && !shouldYield()) {
1023
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1024
- }
955
+ ReactCurrentOwner.current = null;
956
1026
- if (enableProfilerTimer) {
1027
- // If we didn't finish, pause the "actual" render timer.
1028
- // We'll restart it when we resume work.
1029
- pauseActualRenderTimerIfRunning();
1030
- }
957
+ return next;
958
+}
959
+
960
+function workLoop(isAsync) {
961
+ if (!isAsync) {
962
+ // Flush all expired work.
963
+ while (nextUnitOfWork !== null) {
964
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
965
+ }
966
+ } else {
967
+ // Flush asynchronous work until the deadline runs out of time.
968
+ while (nextUnitOfWork !== null && !shouldYield()) {
969
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
970
+ }
971
+
972
+ if (enableProfilerTimer) {
973
+ // If we didn't finish, pause the "actual" render timer.
974
+ // We'll restart it when we resume work.
975
+ pauseActualRenderTimerIfRunning();
976
}
977
}
978
+}
979
1034
- function renderRoot(
1035
- root: FiberRoot,
1036
- expirationTime: ExpirationTime,
1037
- isAsync: boolean,
1038
- ): Fiber | null {
1039
- invariant(
1040
- !isWorking,
1041
- 'renderRoot was called recursively. This error is likely caused ' +
1042
- 'by a bug in React. Please file an issue.',
980
+function renderRoot(
981
+ root: FiberRoot,
982
+ expirationTime: ExpirationTime,
983
+ isAsync: boolean,
984
+): Fiber | null {
985
+ invariant(
986
+ !isWorking,
987
+ 'renderRoot was called recursively. This error is likely caused ' +
988
+ 'by a bug in React. Please file an issue.',
989
+ );
990
+ isWorking = true;
991
+
992
+ // Check if we're starting from a fresh stack, or if we're resuming from
993
+ // previously yielded work.
994
+ if (
995
+ expirationTime !== nextRenderExpirationTime ||
996
+ root !== nextRoot ||
997
+ nextUnitOfWork === null
998
+ ) {
999
+ // Reset the stack and start working from the root.
1000
+ resetStack();
1001
+ nextRoot = root;
1002
+ nextRenderExpirationTime = expirationTime;
1003
+ nextLatestTimeoutMs = -1;
1004
+ nextUnitOfWork = createWorkInProgress(
1005
+ nextRoot.current,
1006
+ null,
1007
+ nextRenderExpirationTime,
1008
);
1044
- isWorking = true;
1009
+ root.pendingCommitExpirationTime = NoWork;
1010
+ }
1011
1046
- // Check if we're starting from a fresh stack, or if we're resuming from
1047
- // previously yielded work.
1048
- if (
1049
- expirationTime !== nextRenderExpirationTime ||
1050
- root !== nextRoot ||
1051
- nextUnitOfWork === null
1052
- ) {
1053
- // Reset the stack and start working from the root.
1054
- resetStack();
1055
- nextRoot = root;
1056
- nextRenderExpirationTime = expirationTime;
1057
- nextLatestTimeoutMs = -1;
1058
- nextUnitOfWork = createWorkInProgress(
1059
- nextRoot.current,
1060
- null,
1061
- nextRenderExpirationTime,
1062
- );
1063
- root.pendingCommitExpirationTime = NoWork;
1064
- }
1012
+ let didFatal = false;
1013
1066
- let didFatal = false;
1014
+ nextRenderIsExpired =
1015
+ !isAsync || nextRenderExpirationTime <= mostRecentCurrentTime;
1016
1068
- nextRenderIsExpired =
1069
- !isAsync || nextRenderExpirationTime <= mostRecentCurrentTime;
1017
+ startWorkLoopTimer(nextUnitOfWork);
1018
1071
- startWorkLoopTimer(nextUnitOfWork);
1019
+ do {
1020
+ try {
1021
+ workLoop(isAsync);
1022
+ } catch (thrownValue) {
1023
+ if (enableProfilerTimer) {
1024
+ // Stop "base" render timer in the event of an error.
1025
+ stopBaseRenderTimerIfRunning();
1026
+ }
1027
1073
- do {
1074
- try {
1075
- workLoop(isAsync);
1076
- } catch (thrownValue) {
1077
- if (enableProfilerTimer) {
1078
- // Stop "base" render timer in the event of an error.
1079
- stopBaseRenderTimerIfRunning();
1028
+ if (nextUnitOfWork === null) {
1029
+ // This is a fatal error.
1030
+ didFatal = true;
1031
+ onUncaughtError(thrownValue);
1032
+ } else {
1033
+ if (__DEV__) {
1034
+ // Reset global debug state
1035
+ // We assume this is defined in DEV
1036
+ (resetCurrentlyProcessingQueue: any)();
1037
}
1038
1082
- if (nextUnitOfWork === null) {
1083
- // This is a fatal error.
1084
- didFatal = true;
1085
- onUncaughtError(thrownValue);
1086
- } else {
1087
- if (__DEV__) {
1088
- // Reset global debug state
1089
- // We assume this is defined in DEV
1090
- (resetCurrentlyProcessingQueue: any)();
1091
- }
1092
-
1093
- const failedUnitOfWork: Fiber = nextUnitOfWork;
1094
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1095
- replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
1096
- }
1039
+ const failedUnitOfWork: Fiber = nextUnitOfWork;
1040
+ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1041
+ replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
1042
+ }
1043
1098
- // TODO: we already know this isn't true in some cases.
1099
- // At least this shows a nicer error message until we figure out the cause.
1100
- // https://github.com/facebook/react/issues/12449#issuecomment-386727431
1101
- invariant(
1102
- nextUnitOfWork !== null,
1103
- 'Failed to replay rendering after an error. This ' +
1104
- 'is likely caused by a bug in React. Please file an issue ' +
1105
- 'with a reproducing case to help us find it.',
1106
- );
1044
+ // TODO: we already know this isn't true in some cases.
1045
+ // At least this shows a nicer error message until we figure out the cause.
1046
+ // https://github.com/facebook/react/issues/12449#issuecomment-386727431
1047
+ invariant(
1048
+ nextUnitOfWork !== null,
1049
+ 'Failed to replay rendering after an error. This ' +
1050
+ 'is likely caused by a bug in React. Please file an issue ' +
1051
+ 'with a reproducing case to help us find it.',
1052
+ );
1053
1108
- const sourceFiber: Fiber = nextUnitOfWork;
1109
- let returnFiber = sourceFiber.return;
1110
- if (returnFiber === null) {
1111
- // This is the root. The root could capture its own errors. However,
1112
- // we don't know if it errors before or after we pushed the host
1113
- // context. This information is needed to avoid a stack mismatch.
1114
- // Because we're not sure, treat this as a fatal error. We could track
1115
- // which phase it fails in, but doesn't seem worth it. At least
1116
- // for now.
1117
- didFatal = true;
1118
- onUncaughtError(thrownValue);
1119
- break;
1120
- }
1121
- throwException(
1122
- root,
1123
- returnFiber,
1124
- sourceFiber,
1125
- thrownValue,
1126
- nextRenderIsExpired,
1127
- nextRenderExpirationTime,
1128
- mostRecentCurrentTimeMs,
1129
- );
1130
- nextUnitOfWork = completeUnitOfWork(sourceFiber);
1054
+ const sourceFiber: Fiber = nextUnitOfWork;
1055
+ let returnFiber = sourceFiber.return;
1056
+ if (returnFiber === null) {
1057
+ // This is the root. The root could capture its own errors. However,
1058
+ // we don't know if it errors before or after we pushed the host
1059
+ // context. This information is needed to avoid a stack mismatch.
1060
+ // Because we're not sure, treat this as a fatal error. We could track
1061
+ // which phase it fails in, but doesn't seem worth it. At least
1062
+ // for now.
1063
+ didFatal = true;
1064
+ onUncaughtError(thrownValue);
1065
+ break;
1066
}
1067
+ throwException(
1068
+ root,
1069
+ returnFiber,
1070
+ sourceFiber,
1071
+ thrownValue,
1072
+ nextRenderIsExpired,
1073
+ nextRenderExpirationTime,
1074
+ mostRecentCurrentTimeMs,
1075
+ );
1076
+ nextUnitOfWork = completeUnitOfWork(sourceFiber);
1077
}
1133
- break;
1134
- } while (true);
1078
+ }
1079
+ break;
1080
+ } while (true);
1081
1136
- // We're done performing work. Time to clean up.
1137
- let didCompleteRoot = false;
1138
- isWorking = false;
1082
+ // We're done performing work. Time to clean up.
1083
+ let didCompleteRoot = false;
1084
+ isWorking = false;
1085
1140
- // Yield back to main thread.
1141
- if (didFatal) {
1086
+ // Yield back to main thread.
1087
+ if (didFatal) {
1088
+ stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1089
+ interruptedBy = null;
1090
+ // There was a fatal error.
1091
+ if (__DEV__) {
1092
+ resetStackAfterFatalErrorInDev();
1093
+ }
1094
+ return null;
1095
+ } else if (nextUnitOfWork === null) {
1096
+ // We reached the root.
1097
+ if (isRootReadyForCommit) {
1098
+ didCompleteRoot = true;
1099
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1100
interruptedBy = null;
1144
- // There was a fatal error.
1145
- if (__DEV__) {
1146
- stack.resetStackAfterFatalErrorInDev();
1147
- }
1148
- return null;
1149
- } else if (nextUnitOfWork === null) {
1150
- // We reached the root.
1151
- if (isRootReadyForCommit) {
1152
- didCompleteRoot = true;
1153
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1154
- interruptedBy = null;
1155
- // The root successfully completed. It's ready for commit.
1156
- root.pendingCommitExpirationTime = expirationTime;
1157
- const finishedWork = root.current.alternate;
1158
- return finishedWork;
1159
- } else {
1160
- // The root did not complete.
1161
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1162
- interruptedBy = null;
1163
- invariant(
1164
- !nextRenderIsExpired,
1165
- 'Expired work should have completed. This error is likely caused ' +
1166
- 'by a bug in React. Please file an issue.',
1167
- );
1168
- markSuspendedPriorityLevel(root, expirationTime);
1169
- if (nextLatestTimeoutMs >= 0) {
1170
- setTimeout(() => {
1171
- retrySuspendedRoot(root, expirationTime);
1172
- }, nextLatestTimeoutMs);
1173
- }
1174
- const firstUnblockedExpirationTime = findNextPendingPriorityLevel(root);
1175
- onBlock(firstUnblockedExpirationTime);
1176
- return null;
1177
- }
1101
+ // The root successfully completed. It's ready for commit.
1102
+ root.pendingCommitExpirationTime = expirationTime;
1103
+ const finishedWork = root.current.alternate;
1104
+ return finishedWork;
1105
} else {
1106
+ // The root did not complete.
1107
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1108
interruptedBy = null;
1181
- // There's more work to do, but we ran out of time. Yield back to
1182
- // the renderer.
1109
+ invariant(
1110
+ !nextRenderIsExpired,
1111
+ 'Expired work should have completed. This error is likely caused ' +
1112
+ 'by a bug in React. Please file an issue.',
1113
+ );
1114
+ markSuspendedPriorityLevel(root, expirationTime);
1115
+ if (nextLatestTimeoutMs >= 0) {
1116
+ setTimeout(() => {
1117
+ retrySuspendedRoot(root, expirationTime);
1118
+ }, nextLatestTimeoutMs);
1119
+ }
1120
+ const firstUnblockedExpirationTime = findNextPendingPriorityLevel(root);
1121
+ onBlock(firstUnblockedExpirationTime);
1122
return null;
1123
}
1124
+ } else {
1125
+ stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1126
+ interruptedBy = null;
1127
+ // There's more work to do, but we ran out of time. Yield back to
1128
+ // the renderer.
1129
+ return null;
1130
}
1131
+}
1132
1187
- function dispatch(
1188
- sourceFiber: Fiber,
1189
- value: mixed,
1190
- expirationTime: ExpirationTime,
1191
- ) {
1192
- invariant(
1193
- !isWorking || isCommitting,
1194
- 'dispatch: Cannot dispatch during the render phase.',
1195
- );
1133
+function dispatch(
1134
+ sourceFiber: Fiber,
1135
+ value: mixed,
1136
+ expirationTime: ExpirationTime,
1137
+) {
1138
+ invariant(
1139
+ !isWorking || isCommitting,
1140
+ 'dispatch: Cannot dispatch during the render phase.',
1141
+ );
1142
1197
- let fiber = sourceFiber.return;
1198
- while (fiber !== null) {
1199
- switch (fiber.tag) {
1200
- case ClassComponent:
1201
- const ctor = fiber.type;
1202
- const instance = fiber.stateNode;
1203
- if (
1204
- typeof ctor.getDerivedStateFromCatch === 'function' ||
1205
- (typeof instance.componentDidCatch === 'function' &&
1206
- !isAlreadyFailedLegacyErrorBoundary(instance))
1207
- ) {
1208
- const errorInfo = createCapturedValue(value, sourceFiber);
1209
- const update = createClassErrorUpdate(
1210
- fiber,
1211
- errorInfo,
1212
- expirationTime,
1213
- );
1214
- enqueueUpdate(fiber, update, expirationTime);
1215
- scheduleWork(fiber, expirationTime);
1216
- return;
1217
- }
1218
- break;
1219
- case HostRoot: {
1143
+ let fiber = sourceFiber.return;
1144
+ while (fiber !== null) {
1145
+ switch (fiber.tag) {
1146
+ case ClassComponent:
1147
+ const ctor = fiber.type;
1148
+ const instance = fiber.stateNode;
1149
+ if (
1150
+ typeof ctor.getDerivedStateFromCatch === 'function' ||
1151
+ (typeof instance.componentDidCatch === 'function' &&
1152
+ !isAlreadyFailedLegacyErrorBoundary(instance))
1153
+ ) {
1154
const errorInfo = createCapturedValue(value, sourceFiber);
1221
- const update = createRootErrorUpdate(
1155
+ const update = createClassErrorUpdate(
1156
fiber,
1157
errorInfo,
1158
expirationTime,
1161
scheduleWork(fiber, expirationTime);
1162
return;
1163
}
1164
+ break;
1165
+ case HostRoot: {
1166
+ const errorInfo = createCapturedValue(value, sourceFiber);
1167
+ const update = createRootErrorUpdate(fiber, errorInfo, expirationTime);
1168
+ enqueueUpdate(fiber, update, expirationTime);
1169
+ scheduleWork(fiber, expirationTime);
1170
+ return;
1171
}
1231
- fiber = fiber.return;
1232
- }
1233
-
1234
- if (sourceFiber.tag === HostRoot) {
1235
- // Error was thrown at the root. There is no parent, so the root
1236
- // itself should capture it.
1237
- const rootFiber = sourceFiber;
1238
- const errorInfo = createCapturedValue(value, rootFiber);
1239
- const update = createRootErrorUpdate(
1240
- rootFiber,
1241
- errorInfo,
1242
- expirationTime,
1243
- );
1244
- enqueueUpdate(rootFiber, update, expirationTime);
1245
- scheduleWork(rootFiber, expirationTime);
1172
}
1173
+ fiber = fiber.return;
1174
}
1175
1249
- function onCommitPhaseError(fiber: Fiber, error: mixed) {
1250
- return dispatch(fiber, error, Sync);
1176
+ if (sourceFiber.tag === HostRoot) {
1177
+ // Error was thrown at the root. There is no parent, so the root
1178
+ // itself should capture it.
1179
+ const rootFiber = sourceFiber;
1180
+ const errorInfo = createCapturedValue(value, rootFiber);
1181
+ const update = createRootErrorUpdate(rootFiber, errorInfo, expirationTime);
1182
+ enqueueUpdate(rootFiber, update, expirationTime);
1183
+ scheduleWork(rootFiber, expirationTime);
1184
}
1185
+}
1186
+
1187
+function captureCommitPhaseError(fiber: Fiber, error: mixed) {
1188
+ return dispatch(fiber, error, Sync);
1189
+}
1190
1253
- function computeAsyncExpiration(currentTime: ExpirationTime) {
1254
- // Given the current clock time, returns an expiration time. We use rounding
1255
- // to batch like updates together.
1256
- // Should complete within ~1000ms. 1200ms max.
1257
- const expirationMs = 5000;
1258
- const bucketSizeMs = 250;
1259
- return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1191
+function computeAsyncExpiration(currentTime: ExpirationTime) {
1192
+ // Given the current clock time, returns an expiration time. We use rounding
1193
+ // to batch like updates together.
1194
+ // Should complete within ~1000ms. 1200ms max.
1195
+ const expirationMs = 5000;
1196
+ const bucketSizeMs = 250;
1197
+ return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1198
+}
1199
+
1200
+function computeInteractiveExpiration(currentTime: ExpirationTime) {
1201
+ let expirationMs;
1202
+ // We intentionally set a higher expiration time for interactive updates in
1203
+ // dev than in production.
1204
+ // If the main thread is being blocked so long that you hit the expiration,
1205
+ // it's a problem that could be solved with better scheduling.
1206
+ // People will be more likely to notice this and fix it with the long
1207
+ // expiration time in development.
1208
+ // In production we opt for better UX at the risk of masking scheduling
1209
+ // problems, by expiring fast.
1210
+ if (__DEV__) {
1211
+ // Should complete within ~500ms. 600ms max.
1212
+ expirationMs = 500;
1213
+ } else {
1214
+ // In production things should be more responsive, 150ms max.
1215
+ expirationMs = 150;
1216
}
1217
+ const bucketSizeMs = 100;
1218
+ return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1219
+}
1220
1262
- function computeInteractiveExpiration(currentTime: ExpirationTime) {
1263
- let expirationMs;
1264
- // We intentionally set a higher expiration time for interactive updates in
1265
- // dev than in production.
1266
- // If the main thread is being blocked so long that you hit the expiration,
1267
- // it's a problem that could be solved with better scheduling.
1268
- // People will be more likely to notice this and fix it with the long
1269
- // expiration time in development.
1270
- // In production we opt for better UX at the risk of masking scheduling
1271
- // problems, by expiring fast.
1272
- if (__DEV__) {
1273
- // Should complete within ~500ms. 600ms max.
1274
- expirationMs = 500;
1275
- } else {
1276
- // In production things should be more responsive, 150ms max.
1277
- expirationMs = 150;
1278
- }
1279
- const bucketSizeMs = 100;
1280
- return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1281
- }
1282
-
1283
- // Creates a unique async expiration time.
1284
- function computeUniqueAsyncExpiration(): ExpirationTime {
1285
- const currentTime = recalculateCurrentTime();
1286
- let result = computeAsyncExpiration(currentTime);
1287
- if (result <= lastUniqueAsyncExpiration) {
1288
- // Since we assume the current time monotonically increases, we only hit
1289
- // this branch when computeUniqueAsyncExpiration is fired multiple times
1290
- // within a 200ms window (or whatever the async bucket size is).
1291
- result = lastUniqueAsyncExpiration + 1;
1292
- }
1293
- lastUniqueAsyncExpiration = result;
1294
- return lastUniqueAsyncExpiration;
1221
+// Creates a unique async expiration time.
1222
+function computeUniqueAsyncExpiration(): ExpirationTime {
1223
+ const currentTime = recalculateCurrentTime();
1224
+ let result = computeAsyncExpiration(currentTime);
1225
+ if (result <= lastUniqueAsyncExpiration) {
1226
+ // Since we assume the current time monotonically increases, we only hit
1227
+ // this branch when computeUniqueAsyncExpiration is fired multiple times
1228
+ // within a 200ms window (or whatever the async bucket size is).
1229
+ result = lastUniqueAsyncExpiration + 1;
1230
}
1231
+ lastUniqueAsyncExpiration = result;
1232
+ return lastUniqueAsyncExpiration;
1233
+}
1234
1297
- function computeExpirationForFiber(
1298
- currentTime: ExpirationTime,
1299
- fiber: Fiber,
1300
- ) {
1301
- let expirationTime;
1302
- if (expirationContext !== NoWork) {
1303
- // An explicit expiration context was set;
1304
- expirationTime = expirationContext;
1305
- } else if (isWorking) {
1306
- if (isCommitting) {
1307
- // Updates that occur during the commit phase should have sync priority
1308
- // by default.
1309
- expirationTime = Sync;
1310
- } else {
1311
- // Updates during the render phase should expire at the same time as
1312
- // the work that is being rendered.
1313
- expirationTime = nextRenderExpirationTime;
1314
- }
1235
+function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
1236
+ let expirationTime;
1237
+ if (expirationContext !== NoWork) {
1238
+ // An explicit expiration context was set;
1239
+ expirationTime = expirationContext;
1240
+ } else if (isWorking) {
1241
+ if (isCommitting) {
1242
+ // Updates that occur during the commit phase should have sync priority
1243
+ // by default.
1244
+ expirationTime = Sync;
1245
} else {
1316
- // No explicit expiration context was set, and we're not currently
1317
- // performing work. Calculate a new expiration time.
1318
- if (fiber.mode & AsyncMode) {
1319
- if (isBatchingInteractiveUpdates) {
1320
- // This is an interactive update
1321
- expirationTime = computeInteractiveExpiration(currentTime);
1322
- } else {
1323
- // This is an async update
1324
- expirationTime = computeAsyncExpiration(currentTime);
1325
- }
1246
+ // Updates during the render phase should expire at the same time as
1247
+ // the work that is being rendered.
1248
+ expirationTime = nextRenderExpirationTime;
1249
+ }
1250
+ } else {
1251
+ // No explicit expiration context was set, and we're not currently
1252
+ // performing work. Calculate a new expiration time.
1253
+ if (fiber.mode & AsyncMode) {
1254
+ if (isBatchingInteractiveUpdates) {
1255
+ // This is an interactive update
1256
+ expirationTime = computeInteractiveExpiration(currentTime);
1257
} else {
1327
- // This is a sync update
1328
- expirationTime = Sync;
1258
+ // This is an async update
1259
+ expirationTime = computeAsyncExpiration(currentTime);
1260
}
1261
+ } else {
1262
+ // This is a sync update
1263
+ expirationTime = Sync;
1264
}
1331
- if (isBatchingInteractiveUpdates) {
1332
- // This is an interactive update. Keep track of the lowest pending
1333
- // interactive expiration time. This allows us to synchronously flush
1334
- // all interactive updates when needed.
1335
- if (
1336
- lowestPendingInteractiveExpirationTime === NoWork ||
1337
- expirationTime > lowestPendingInteractiveExpirationTime
1338
- ) {
1339
- lowestPendingInteractiveExpirationTime = expirationTime;
1340
- }
1265
+ }
1266
+ if (isBatchingInteractiveUpdates) {
1267
+ // This is an interactive update. Keep track of the lowest pending
1268
+ // interactive expiration time. This allows us to synchronously flush
1269
+ // all interactive updates when needed.
1270
+ if (
1271
+ lowestPendingInteractiveExpirationTime === NoWork ||
1272
+ expirationTime > lowestPendingInteractiveExpirationTime
1273
+ ) {
1274
+ lowestPendingInteractiveExpirationTime = expirationTime;
1275
}
1342
- return expirationTime;
1276
}
1277
+ return expirationTime;
1278
+}
1279
1345
- // TODO: Rename this to scheduleTimeout or something
1346
- function suspendRoot(
1347
- root: FiberRoot,
1348
- thenable: Thenable,
1349
- timeoutMs: number,
1350
- suspendedTime: ExpirationTime,
1351
- ) {
1352
- // Schedule the timeout.
1353
- if (timeoutMs >= 0 && nextLatestTimeoutMs < timeoutMs) {
1354
- nextLatestTimeoutMs = timeoutMs;
1355
- }
1280
+// TODO: Rename this to scheduleTimeout or something
1281
+function suspendRoot(
1282
+ root: FiberRoot,
1283
+ thenable: Thenable,
1284
+ timeoutMs: number,
1285
+ suspendedTime: ExpirationTime,
1286
+) {
1287
+ // Schedule the timeout.
1288
+ if (timeoutMs >= 0 && nextLatestTimeoutMs < timeoutMs) {
1289
+ nextLatestTimeoutMs = timeoutMs;
1290
}
1291
+}
1292
1358
- function retrySuspendedRoot(root, suspendedTime) {
1359
- markPingedPriorityLevel(root, suspendedTime);
1360
- const retryTime = findNextPendingPriorityLevel(root);
1361
- if (retryTime !== NoWork) {
1362
- requestRetry(root, retryTime);
1363
- }
1293
+function retrySuspendedRoot(root: FiberRoot, suspendedTime: ExpirationTime) {
1294
+ markPingedPriorityLevel(root, suspendedTime);
1295
+ const retryTime = findNextPendingPriorityLevel(root);
1296
+ if (retryTime !== NoWork) {
1297
+ requestRetry(root, retryTime);
1298
}
1299
+}
1300
1366
- function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1367
- recordScheduleUpdate();
1301
+function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1302
+ recordScheduleUpdate();
1303
1369
- if (__DEV__) {
1370
- if (fiber.tag === ClassComponent) {
1371
- const instance = fiber.stateNode;
1372
- warnAboutInvalidUpdates(instance);
1373
- }
1304
+ if (__DEV__) {
1305
+ if (fiber.tag === ClassComponent) {
1306
+ const instance = fiber.stateNode;
1307
+ warnAboutInvalidUpdates(instance);
1308
}
1309
+ }
1310
1376
- let node = fiber;
1377
- while (node !== null) {
1378
- // Walk the parent path to the root and update each node's
1379
- // expiration time.
1311
+ let node = fiber;
1312
+ while (node !== null) {
1313
+ // Walk the parent path to the root and update each node's
1314
+ // expiration time.
1315
+ if (
1316
+ node.expirationTime === NoWork ||
1317
+ node.expirationTime > expirationTime
1318
+ ) {
1319
+ node.expirationTime = expirationTime;
1320
+ }
1321
+ if (node.alternate !== null) {
1322
if (
1381
- node.expirationTime === NoWork ||
1382
- node.expirationTime > expirationTime
1323
+ node.alternate.expirationTime === NoWork ||
1324
+ node.alternate.expirationTime > expirationTime
1325
) {
1384
- node.expirationTime = expirationTime;
1326
+ node.alternate.expirationTime = expirationTime;
1327
}
1386
- if (node.alternate !== null) {
1328
+ }
1329
+ if (node.return === null) {
1330
+ if (node.tag === HostRoot) {
1331
+ const root: FiberRoot = (node.stateNode: any);
1332
if (
1388
- node.alternate.expirationTime === NoWork ||
1389
- node.alternate.expirationTime > expirationTime
1333
+ !isWorking &&
1334
+ nextRenderExpirationTime !== NoWork &&
1335
+ expirationTime < nextRenderExpirationTime
1336
) {
1391
- node.alternate.expirationTime = expirationTime;
1337
+ // This is an interruption. (Used for performance tracking.)
1338
+ interruptedBy = fiber;
1339
+ resetStack();
1340
}
1393
- }
1394
- if (node.return === null) {
1395
- if (node.tag === HostRoot) {
1396
- const root: FiberRoot = (node.stateNode: any);
1397
- if (
1398
- !isWorking &&
1399
- nextRenderExpirationTime !== NoWork &&
1400
- expirationTime < nextRenderExpirationTime
1401
- ) {
1402
- // This is an interruption. (Used for performance tracking.)
1403
- interruptedBy = fiber;
1404
- resetStack();
1405
- }
1406
- markPendingPriorityLevel(root, expirationTime);
1407
- const nextExpirationTimeToWorkOn = findNextPendingPriorityLevel(root);
1408
- if (
1409
- // If we're in the render phase, we don't need to schedule this root
1410
- // for an update, because we'll do it before we exit...
1411
- !isWorking ||
1412
- isCommitting ||
1413
- // ...unless this is a different root than the one we're rendering.
1414
- nextRoot !== root
1415
- ) {
1416
- requestWork(root, nextExpirationTimeToWorkOn);
1417
- }
1418
- if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1419
- invariant(
1420
- false,
1421
- 'Maximum update depth exceeded. This can happen when a ' +
1422
- 'component repeatedly calls setState inside ' +
1423
- 'componentWillUpdate or componentDidUpdate. React limits ' +
1424
- 'the number of nested updates to prevent infinite loops.',
1425
- );
1426
- }
1427
- } else {
1428
- if (__DEV__) {
1429
- if (fiber.tag === ClassComponent) {
1430
- warnAboutUpdateOnUnmounted(fiber);
1431
- }
1341
+ markPendingPriorityLevel(root, expirationTime);
1342
+ const nextExpirationTimeToWorkOn = findNextPendingPriorityLevel(root);
1343
+ if (
1344
+ // If we're in the render phase, we don't need to schedule this root
1345
+ // for an update, because we'll do it before we exit...
1346
+ !isWorking ||
1347
+ isCommitting ||
1348
+ // ...unless this is a different root than the one we're rendering.
1349
+ nextRoot !== root
1350
+ ) {
1351
+ requestWork(root, nextExpirationTimeToWorkOn);
1352
+ }
1353
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1354
+ invariant(
1355
+ false,
1356
+ 'Maximum update depth exceeded. This can happen when a ' +
1357
+ 'component repeatedly calls setState inside ' +
1358
+ 'componentWillUpdate or componentDidUpdate. React limits ' +
1359
+ 'the number of nested updates to prevent infinite loops.',
1360
+ );
1361
+ }
1362
+ } else {
1363
+ if (__DEV__) {
1364
+ if (fiber.tag === ClassComponent) {
1365
+ warnAboutUpdateOnUnmounted(fiber);
1366
}
1433
- return;
1367
}
1368
+ return;
1369
}
1436
- node = node.return;
1370
}
1371
+ node = node.return;
1372
}
1373
+}
1374
1440
- function recalculateCurrentTime(): ExpirationTime {
1441
- // Subtract initial time so it fits inside 32bits
1442
- mostRecentCurrentTimeMs = now() - originalStartTimeMs;
1443
- mostRecentCurrentTime = msToExpirationTime(mostRecentCurrentTimeMs);
1444
- return mostRecentCurrentTime;
1445
- }
1375
+function recalculateCurrentTime(): ExpirationTime {
1376
+ // Subtract initial time so it fits inside 32bits
1377
+ mostRecentCurrentTimeMs = now() - originalStartTimeMs;
1378
+ mostRecentCurrentTime = msToExpirationTime(mostRecentCurrentTimeMs);
1379
+ return mostRecentCurrentTime;
1380
+}
1381
1447
- function deferredUpdates<A>(fn: () => A): A {
1448
- const previousExpirationContext = expirationContext;
1449
- const currentTime = recalculateCurrentTime();
1450
- expirationContext = computeAsyncExpiration(currentTime);
1451
- try {
1452
- return fn();
1453
- } finally {
1454
- expirationContext = previousExpirationContext;
1455
- }
1382
+function deferredUpdates<A>(fn: () => A): A {
1383
+ const previousExpirationContext = expirationContext;
1384
+ const currentTime = recalculateCurrentTime();
1385
+ expirationContext = computeAsyncExpiration(currentTime);
1386
+ try {
1387
+ return fn();
1388
+ } finally {
1389
+ expirationContext = previousExpirationContext;
1390
}
1457
- function syncUpdates<A, B, C0, D, R>(
1458
- fn: (A, B, C0, D) => R,
1459
- a: A,
1460
- b: B,
1461
- c: C0,
1462
- d: D,
1463
- ): R {
1464
- const previousExpirationContext = expirationContext;
1465
- expirationContext = Sync;
1466
- try {
1467
- return fn(a, b, c, d);
1468
- } finally {
1469
- expirationContext = previousExpirationContext;
1470
- }
1391
+}
1392
+function syncUpdates<A, B, C0, D, R>(
1393
+ fn: (A, B, C0, D) => R,
1394
+ a: A,
1395
+ b: B,
1396
+ c: C0,
1397
+ d: D,
1398
+): R {
1399
+ const previousExpirationContext = expirationContext;
1400
+ expirationContext = Sync;
1401
+ try {
1402
+ return fn(a, b, c, d);
1403
+ } finally {
1404
+ expirationContext = previousExpirationContext;
1405
}
1406
+}
1407
1473
- // TODO: Everything below this is written as if it has been lifted to the
1474
- // renderers. I'll do this in a follow-up.
1475
-
1476
- // Linked-list of roots
1477
- let firstScheduledRoot: FiberRoot | null = null;
1478
- let lastScheduledRoot: FiberRoot | null = null;
1479
-
1480
- let callbackExpirationTime: ExpirationTime = NoWork;
1481
- let callbackID: number = -1;
1482
- let isRendering: boolean = false;
1483
- let nextFlushedRoot: FiberRoot | null = null;
1484
- let nextFlushedExpirationTime: ExpirationTime = NoWork;
1485
- let lowestPendingInteractiveExpirationTime: ExpirationTime = NoWork;
1486
- let deadlineDidExpire: boolean = false;
1487
- let hasUnhandledError: boolean = false;
1488
- let unhandledError: mixed | null = null;
1489
- let deadline: Deadline | null = null;
1490
-
1491
- let isBatchingUpdates: boolean = false;
1492
- let isUnbatchingUpdates: boolean = false;
1493
- let isBatchingInteractiveUpdates: boolean = false;
1494
-
1495
- let completedBatches: Array<Batch> | null = null;
1496
-
1497
- // Use these to prevent an infinite loop of nested updates
1498
- const NESTED_UPDATE_LIMIT = 1000;
1499
- let nestedUpdateCount: number = 0;
1500
-
1501
- const timeHeuristicForUnitOfWork = 1;
1502
-
1503
- function scheduleCallbackWithExpiration(expirationTime) {
1504
- if (callbackExpirationTime !== NoWork) {
1505
- // A callback is already scheduled. Check its expiration time (timeout).
1506
- if (expirationTime > callbackExpirationTime) {
1507
- // Existing callback has sufficient timeout. Exit.
1508
- return;
1509
- } else {
1510
- // Existing callback has insufficient timeout. Cancel and schedule a
1511
- // new one.
1512
- cancelDeferredCallback(callbackID);
1513
- }
1514
- // The request callback timer is already running. Don't start a new one.
1408
+// TODO: Everything below this is written as if it has been lifted to the
1409
+// renderers. I'll do this in a follow-up.
1410
+
1411
+// Linked-list of roots
1412
+let firstScheduledRoot: FiberRoot | null = null;
1413
+let lastScheduledRoot: FiberRoot | null = null;
1414
+
1415
+let callbackExpirationTime: ExpirationTime = NoWork;
1416
+let callbackID: number = -1;
1417
+let isRendering: boolean = false;
1418
+let nextFlushedRoot: FiberRoot | null = null;
1419
+let nextFlushedExpirationTime: ExpirationTime = NoWork;
1420
+let lowestPendingInteractiveExpirationTime: ExpirationTime = NoWork;
1421
+let deadlineDidExpire: boolean = false;
1422
+let hasUnhandledError: boolean = false;
1423
+let unhandledError: mixed | null = null;
1424
+let deadline: Deadline | null = null;
1425
+
1426
+let isBatchingUpdates: boolean = false;
1427
+let isUnbatchingUpdates: boolean = false;
1428
+let isBatchingInteractiveUpdates: boolean = false;
1429
+
1430
+let completedBatches: Array<Batch> | null = null;
1431
+
1432
+// Use these to prevent an infinite loop of nested updates
1433
+const NESTED_UPDATE_LIMIT = 1000;
1434
+let nestedUpdateCount: number = 0;
1435
+
1436
+const timeHeuristicForUnitOfWork = 1;
1437
+
1438
+function scheduleCallbackWithExpiration(expirationTime) {
1439
+ if (callbackExpirationTime !== NoWork) {
1440
+ // A callback is already scheduled. Check its expiration time (timeout).
1441
+ if (expirationTime > callbackExpirationTime) {
1442
+ // Existing callback has sufficient timeout. Exit.
1443
+ return;
1444
} else {
1516
- startRequestCallbackTimer();
1445
+ // Existing callback has insufficient timeout. Cancel and schedule a
1446
+ // new one.
1447
+ cancelDeferredCallback(callbackID);
1448
}
1449
+ // The request callback timer is already running. Don't start a new one.
1450
+ } else {
1451
+ startRequestCallbackTimer();
1452
+ }
1453
1519
- // Compute a timeout for the given expiration time.
1520
- const currentMs = now() - originalStartTimeMs;
1521
- const expirationMs = expirationTimeToMs(expirationTime);
1522
- const timeout = expirationMs - currentMs;
1454
+ // Compute a timeout for the given expiration time.
1455
+ const currentMs = now() - originalStartTimeMs;
1456
+ const expirationMs = expirationTimeToMs(expirationTime);
1457
+ const timeout = expirationMs - currentMs;
1458
1524
- callbackExpirationTime = expirationTime;
1525
- callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
1526
- }
1459
+ callbackExpirationTime = expirationTime;
1460
+ callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
1461
+}
1462
1528
- function requestRetry(root: FiberRoot, expirationTime: ExpirationTime) {
1529
- if (
1530
- root.remainingExpirationTime === NoWork ||
1531
- root.remainingExpirationTime < expirationTime
1532
- ) {
1533
- // For a retry, only update the remaining expiration time if it has a
1534
- // *lower priority* than the existing value. This is because, on a retry,
1535
- // we should attempt to coalesce as much as possible.
1536
- requestWork(root, expirationTime);
1537
- }
1463
+function requestRetry(root: FiberRoot, expirationTime: ExpirationTime) {
1464
+ if (
1465
+ root.remainingExpirationTime === NoWork ||
1466
+ root.remainingExpirationTime < expirationTime
1467
+ ) {
1468
+ // For a retry, only update the remaining expiration time if it has a
1469
+ // *lower priority* than the existing value. This is because, on a retry,
1470
+ // we should attempt to coalesce as much as possible.
1471
+ requestWork(root, expirationTime);
1472
}
1473
+}
1474
1540
- // requestWork is called by the scheduler whenever a root receives an update.
1541
- // It's up to the renderer to call renderRoot at some point in the future.
1542
- function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
1543
- addRootToSchedule(root, expirationTime);
1475
+// requestWork is called by the scheduler whenever a root receives an update.
1476
+// It's up to the renderer to call renderRoot at some point in the future.
1477
+function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
1478
+ addRootToSchedule(root, expirationTime);
1479
1545
- if (isRendering) {
1546
- // Prevent reentrancy. Remaining work will be scheduled at the end of
1547
- // the currently rendering batch.
1548
- return;
1549
- }
1480
+ if (isRendering) {
1481
+ // Prevent reentrancy. Remaining work will be scheduled at the end of
1482
+ // the currently rendering batch.
1483
+ return;
1484
+ }
1485
1551
- if (isBatchingUpdates) {
1552
- // Flush work at the end of the batch.
1553
- if (isUnbatchingUpdates) {
1554
- // ...unless we're inside unbatchedUpdates, in which case we should
1555
- // flush it now.
1556
- nextFlushedRoot = root;
1557
- nextFlushedExpirationTime = Sync;
1558
- performWorkOnRoot(root, Sync, false);
1559
- }
1560
- return;
1486
+ if (isBatchingUpdates) {
1487
+ // Flush work at the end of the batch.
1488
+ if (isUnbatchingUpdates) {
1489
+ // ...unless we're inside unbatchedUpdates, in which case we should
1490
+ // flush it now.
1491
+ nextFlushedRoot = root;
1492
+ nextFlushedExpirationTime = Sync;
1493
+ performWorkOnRoot(root, Sync, false);
1494
}
1495
+ return;
1496
+ }
1497
1563
- // TODO: Get rid of Sync and use current time?
1564
- if (expirationTime === Sync) {
1565
- performSyncWork();
1566
- } else {
1567
- scheduleCallbackWithExpiration(expirationTime);
1568
- }
1498
+ // TODO: Get rid of Sync and use current time?
1499
+ if (expirationTime === Sync) {
1500
+ performSyncWork();
1501
+ } else {
1502
+ scheduleCallbackWithExpiration(expirationTime);
1503
}
1504
+}
1505
1571
- function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
1572
- // Add the root to the schedule.
1573
- // Check if this root is already part of the schedule.
1574
- if (root.nextScheduledRoot === null) {
1575
- // This root is not already scheduled. Add it.
1576
- root.remainingExpirationTime = expirationTime;
1577
- if (lastScheduledRoot === null) {
1578
- firstScheduledRoot = lastScheduledRoot = root;
1579
- root.nextScheduledRoot = root;
1580
- } else {
1581
- lastScheduledRoot.nextScheduledRoot = root;
1582
- lastScheduledRoot = root;
1583
- lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1584
- }
1506
+function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
1507
+ // Add the root to the schedule.
1508
+ // Check if this root is already part of the schedule.
1509
+ if (root.nextScheduledRoot === null) {
1510
+ // This root is not already scheduled. Add it.
1511
+ root.remainingExpirationTime = expirationTime;
1512
+ if (lastScheduledRoot === null) {
1513
+ firstScheduledRoot = lastScheduledRoot = root;
1514
+ root.nextScheduledRoot = root;
1515
} else {
1586
- // This root is already scheduled, but its priority may have increased.
1587
- const remainingExpirationTime = root.remainingExpirationTime;
1588
- if (
1589
- remainingExpirationTime === NoWork ||
1590
- expirationTime < remainingExpirationTime
1591
- ) {
1592
- // Update the priority.
1593
- root.remainingExpirationTime = expirationTime;
1594
- }
1516
+ lastScheduledRoot.nextScheduledRoot = root;
1517
+ lastScheduledRoot = root;
1518
+ lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1519
+ }
1520
+ } else {
1521
+ // This root is already scheduled, but its priority may have increased.
1522
+ const remainingExpirationTime = root.remainingExpirationTime;
1523
+ if (
1524
+ remainingExpirationTime === NoWork ||
1525
+ expirationTime < remainingExpirationTime
1526
+ ) {
1527
+ // Update the priority.
1528
+ root.remainingExpirationTime = expirationTime;
1529
}
1530
}
1531
+}
1532
1598
- function findHighestPriorityRoot() {
1599
- let highestPriorityWork = NoWork;
1600
- let highestPriorityRoot = null;
1601
- if (lastScheduledRoot !== null) {
1602
- let previousScheduledRoot = lastScheduledRoot;
1603
- let root = firstScheduledRoot;
1604
- while (root !== null) {
1605
- const remainingExpirationTime = root.remainingExpirationTime;
1606
- if (remainingExpirationTime === NoWork) {
1607
- // This root no longer has work. Remove it from the scheduler.
1608
-
1609
- // TODO: This check is redudant, but Flow is confused by the branch
1610
- // below where we set lastScheduledRoot to null, even though we break
1611
- // from the loop right after.
1612
- invariant(
1613
- previousScheduledRoot !== null && lastScheduledRoot !== null,
1614
- 'Should have a previous and last root. This error is likely ' +
1615
- 'caused by a bug in React. Please file an issue.',
1616
- );
1617
- if (root === root.nextScheduledRoot) {
1618
- // This is the only root in the list.
1619
- root.nextScheduledRoot = null;
1620
- firstScheduledRoot = lastScheduledRoot = null;
1621
- break;
1622
- } else if (root === firstScheduledRoot) {
1623
- // This is the first root in the list.
1624
- const next = root.nextScheduledRoot;
1625
- firstScheduledRoot = next;
1626
- lastScheduledRoot.nextScheduledRoot = next;
1627
- root.nextScheduledRoot = null;
1628
- } else if (root === lastScheduledRoot) {
1629
- // This is the last root in the list.
1630
- lastScheduledRoot = previousScheduledRoot;
1631
- lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1632
- root.nextScheduledRoot = null;
1633
- break;
1634
- } else {
1635
- previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
1636
- root.nextScheduledRoot = null;
1637
- }
1638
- root = previousScheduledRoot.nextScheduledRoot;
1533
+function findHighestPriorityRoot() {
1534
+ let highestPriorityWork = NoWork;
1535
+ let highestPriorityRoot = null;
1536
+ if (lastScheduledRoot !== null) {
1537
+ let previousScheduledRoot = lastScheduledRoot;
1538
+ let root = firstScheduledRoot;
1539
+ while (root !== null) {
1540
+ const remainingExpirationTime = root.remainingExpirationTime;
1541
+ if (remainingExpirationTime === NoWork) {
1542
+ // This root no longer has work. Remove it from the scheduler.
1543
+
1544
+ // TODO: This check is redudant, but Flow is confused by the branch
1545
+ // below where we set lastScheduledRoot to null, even though we break
1546
+ // from the loop right after.
1547
+ invariant(
1548
+ previousScheduledRoot !== null && lastScheduledRoot !== null,
1549
+ 'Should have a previous and last root. This error is likely ' +
1550
+ 'caused by a bug in React. Please file an issue.',
1551
+ );
1552
+ if (root === root.nextScheduledRoot) {
1553
+ // This is the only root in the list.
1554
+ root.nextScheduledRoot = null;
1555
+ firstScheduledRoot = lastScheduledRoot = null;
1556
+ break;
1557
+ } else if (root === firstScheduledRoot) {
1558
+ // This is the first root in the list.
1559
+ const next = root.nextScheduledRoot;
1560
+ firstScheduledRoot = next;
1561
+ lastScheduledRoot.nextScheduledRoot = next;
1562
+ root.nextScheduledRoot = null;
1563
+ } else if (root === lastScheduledRoot) {
1564
+ // This is the last root in the list.
1565
+ lastScheduledRoot = previousScheduledRoot;
1566
+ lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1567
+ root.nextScheduledRoot = null;
1568
+ break;
1569
} else {
1640
- if (
1641
- highestPriorityWork === NoWork ||
1642
- remainingExpirationTime < highestPriorityWork
1643
- ) {
1644
- // Update the priority, if it's higher
1645
- highestPriorityWork = remainingExpirationTime;
1646
- highestPriorityRoot = root;
1647
- }
1648
- if (root === lastScheduledRoot) {
1649
- break;
1650
- }
1651
- previousScheduledRoot = root;
1652
- root = root.nextScheduledRoot;
1570
+ previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
1571
+ root.nextScheduledRoot = null;
1572
+ }
1573
+ root = previousScheduledRoot.nextScheduledRoot;
1574
+ } else {
1575
+ if (
1576
+ highestPriorityWork === NoWork ||
1577
+ remainingExpirationTime < highestPriorityWork
1578
+ ) {
1579
+ // Update the priority, if it's higher
1580
+ highestPriorityWork = remainingExpirationTime;
1581
+ highestPriorityRoot = root;
1582
}
1583
+ if (root === lastScheduledRoot) {
1584
+ break;
1585
+ }
1586
+ previousScheduledRoot = root;
1587
+ root = root.nextScheduledRoot;
1588
}
1589
}
1656
-
1657
- // If the next root is the same as the previous root, this is a nested
1658
- // update. To prevent an infinite loop, increment the nested update count.
1659
- const previousFlushedRoot = nextFlushedRoot;
1660
- if (
1661
- previousFlushedRoot !== null &&
1662
- previousFlushedRoot === highestPriorityRoot &&
1663
- highestPriorityWork === Sync
1664
- ) {
1665
- nestedUpdateCount++;
1666
- } else {
1667
- // Reset whenever we switch roots.
1668
- nestedUpdateCount = 0;
1669
- }
1670
- nextFlushedRoot = highestPriorityRoot;
1671
- nextFlushedExpirationTime = highestPriorityWork;
1590
}
1591
1674
- function performAsyncWork(dl) {
1675
- performWork(NoWork, true, dl);
1676
- }
1677
-
1678
- function performSyncWork() {
1679
- performWork(Sync, false, null);
1592
+ // If the next root is the same as the previous root, this is a nested
1593
+ // update. To prevent an infinite loop, increment the nested update count.
1594
+ const previousFlushedRoot = nextFlushedRoot;
1595
+ if (
1596
+ previousFlushedRoot !== null &&
1597
+ previousFlushedRoot === highestPriorityRoot &&
1598
+ highestPriorityWork === Sync
1599
+ ) {
1600
+ nestedUpdateCount++;
1601
+ } else {
1602
+ // Reset whenever we switch roots.
1603
+ nestedUpdateCount = 0;
1604
}
1605
+ nextFlushedRoot = highestPriorityRoot;
1606
+ nextFlushedExpirationTime = highestPriorityWork;
1607
+}
1608
1682
- function performWork(
1683
- minExpirationTime: ExpirationTime,
1684
- isAsync: boolean,
1685
- dl: Deadline | null,
1686
- ) {
1687
- deadline = dl;
1609
+function performAsyncWork(dl) {
1610
+ performWork(NoWork, true, dl);
1611
+}
1612
1689
- // Keep working on roots until there's no more work, or until the we reach
1690
- // the deadline.
1691
- findHighestPriorityRoot();
1613
+function performSyncWork() {
1614
+ performWork(Sync, false, null);
1615
+}
1616
1693
- if (enableProfilerTimer) {
1694
- resumeActualRenderTimerIfPaused();
1695
- }
1617
+function performWork(
1618
+ minExpirationTime: ExpirationTime,
1619
+ isAsync: boolean,
1620
+ dl: Deadline | null,
1621
+) {
1622
+ deadline = dl;
1623
1697
- if (enableUserTimingAPI && deadline !== null) {
1698
- const didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
1699
- const timeout = expirationTimeToMs(nextFlushedExpirationTime);
1700
- stopRequestCallbackTimer(didExpire, timeout);
1701
- }
1624
+ // Keep working on roots until there's no more work, or until the we reach
1625
+ // the deadline.
1626
+ findHighestPriorityRoot();
1627
1703
- if (isAsync) {
1704
- while (
1705
- nextFlushedRoot !== null &&
1706
- nextFlushedExpirationTime !== NoWork &&
1707
- (minExpirationTime === NoWork ||
1708
- minExpirationTime >= nextFlushedExpirationTime) &&
1709
- (!deadlineDidExpire ||
1710
- recalculateCurrentTime() >= nextFlushedExpirationTime)
1711
- ) {
1712
- recalculateCurrentTime();
1713
- performWorkOnRoot(
1714
- nextFlushedRoot,
1715
- nextFlushedExpirationTime,
1716
- !deadlineDidExpire,
1717
- );
1718
- findHighestPriorityRoot();
1719
- }
1720
- } else {
1721
- while (
1722
- nextFlushedRoot !== null &&
1723
- nextFlushedExpirationTime !== NoWork &&
1724
- (minExpirationTime === NoWork ||
1725
- minExpirationTime >= nextFlushedExpirationTime)
1726
- ) {
1727
- performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
1728
- findHighestPriorityRoot();
1729
- }
1730
- }
1628
+ if (enableProfilerTimer) {
1629
+ resumeActualRenderTimerIfPaused();
1630
+ }
1631
1732
- // We're done flushing work. Either we ran out of time in this callback,
1733
- // or there's no more work left with sufficient priority.
1632
+ if (enableUserTimingAPI && deadline !== null) {
1633
+ const didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
1634
+ const timeout = expirationTimeToMs(nextFlushedExpirationTime);
1635
+ stopRequestCallbackTimer(didExpire, timeout);
1636
+ }
1637
1735
- // If we're inside a callback, set this to false since we just completed it.
1736
- if (deadline !== null) {
1737
- callbackExpirationTime = NoWork;
1738
- callbackID = -1;
1739
- }
1740
- // If there's work left over, schedule a new callback.
1741
- if (nextFlushedExpirationTime !== NoWork) {
1742
- scheduleCallbackWithExpiration(nextFlushedExpirationTime);
1638
+ if (isAsync) {
1639
+ while (
1640
+ nextFlushedRoot !== null &&
1641
+ nextFlushedExpirationTime !== NoWork &&
1642
+ (minExpirationTime === NoWork ||
1643
+ minExpirationTime >= nextFlushedExpirationTime) &&
1644
+ (!deadlineDidExpire ||
1645
+ recalculateCurrentTime() >= nextFlushedExpirationTime)
1646
+ ) {
1647
+ recalculateCurrentTime();
1648
+ performWorkOnRoot(
1649
+ nextFlushedRoot,
1650
+ nextFlushedExpirationTime,
1651
+ !deadlineDidExpire,
1652
+ );
1653
+ findHighestPriorityRoot();
1654
+ }
1655
+ } else {
1656
+ while (
1657
+ nextFlushedRoot !== null &&
1658
+ nextFlushedExpirationTime !== NoWork &&
1659
+ (minExpirationTime === NoWork ||
1660
+ minExpirationTime >= nextFlushedExpirationTime)
1661
+ ) {
1662
+ performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
1663
+ findHighestPriorityRoot();
1664
}
1665
+ }
1666
1745
- // Clean-up.
1746
- deadline = null;
1747
- deadlineDidExpire = false;
1667
+ // We're done flushing work. Either we ran out of time in this callback,
1668
+ // or there's no more work left with sufficient priority.
1669
1749
- finishRendering();
1670
+ // If we're inside a callback, set this to false since we just completed it.
1671
+ if (deadline !== null) {
1672
+ callbackExpirationTime = NoWork;
1673
+ callbackID = -1;
1674
}
1751
-
1752
- function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1753
- invariant(
1754
- !isRendering,
1755
- 'work.commit(): Cannot commit while already rendering. This likely ' +
1756
- 'means you attempted to commit from inside a lifecycle method.',
1757
- );
1758
- // Perform work on root as if the given expiration time is the current time.
1759
- // This has the effect of synchronously flushing all work up to and
1760
- // including the given time.
1761
- nextFlushedRoot = root;
1762
- nextFlushedExpirationTime = expirationTime;
1763
- performWorkOnRoot(root, expirationTime, false);
1764
- // Flush any sync work that was scheduled by lifecycles
1765
- performSyncWork();
1766
- finishRendering();
1675
+ // If there's work left over, schedule a new callback.
1676
+ if (nextFlushedExpirationTime !== NoWork) {
1677
+ scheduleCallbackWithExpiration(nextFlushedExpirationTime);
1678
}
1679
1769
- function finishRendering() {
1770
- nestedUpdateCount = 0;
1680
+ // Clean-up.
1681
+ deadline = null;
1682
+ deadlineDidExpire = false;
1683
1772
- if (completedBatches !== null) {
1773
- const batches = completedBatches;
1774
- completedBatches = null;
1775
- for (let i = 0; i < batches.length; i++) {
1776
- const batch = batches[i];
1777
- try {
1778
- batch._onComplete();
1779
- } catch (error) {
1780
- if (!hasUnhandledError) {
1781
- hasUnhandledError = true;
1782
- unhandledError = error;
1783
- }
1684
+ finishRendering();
1685
+}
1686
+
1687
+function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1688
+ invariant(
1689
+ !isRendering,
1690
+ 'work.commit(): Cannot commit while already rendering. This likely ' +
1691
+ 'means you attempted to commit from inside a lifecycle method.',
1692
+ );
1693
+ // Perform work on root as if the given expiration time is the current time.
1694
+ // This has the effect of synchronously flushing all work up to and
1695
+ // including the given time.
1696
+ nextFlushedRoot = root;
1697
+ nextFlushedExpirationTime = expirationTime;
1698
+ performWorkOnRoot(root, expirationTime, false);
1699
+ // Flush any sync work that was scheduled by lifecycles
1700
+ performSyncWork();
1701
+ finishRendering();
1702
+}
1703
+
1704
+function finishRendering() {
1705
+ nestedUpdateCount = 0;
1706
+
1707
+ if (completedBatches !== null) {
1708
+ const batches = completedBatches;
1709
+ completedBatches = null;
1710
+ for (let i = 0; i < batches.length; i++) {
1711
+ const batch = batches[i];
1712
+ try {
1713
+ batch._onComplete();
1714
+ } catch (error) {
1715
+ if (!hasUnhandledError) {
1716
+ hasUnhandledError = true;
1717
+ unhandledError = error;
1718
}
1719
}
1720
}
1721
+ }
1722
1788
- if (hasUnhandledError) {
1789
- const error = unhandledError;
1790
- unhandledError = null;
1791
- hasUnhandledError = false;
1792
- throw error;
1793
- }
1723
+ if (hasUnhandledError) {
1724
+ const error = unhandledError;
1725
+ unhandledError = null;
1726
+ hasUnhandledError = false;
1727
+ throw error;
1728
}
1729
+}
1730
1796
- function performWorkOnRoot(
1797
- root: FiberRoot,
1798
- expirationTime: ExpirationTime,
1799
- isAsync: boolean,
1800
- ) {
1801
- invariant(
1802
- !isRendering,
1803
- 'performWorkOnRoot was called recursively. This error is likely caused ' +
1804
- 'by a bug in React. Please file an issue.',
1805
- );
1731
+function performWorkOnRoot(
1732
+ root: FiberRoot,
1733
+ expirationTime: ExpirationTime,
1734
+ isAsync: boolean,
1735
+) {
1736
+ invariant(
1737
+ !isRendering,
1738
+ 'performWorkOnRoot was called recursively. This error is likely caused ' +
1739
+ 'by a bug in React. Please file an issue.',
1740
+ );
1741
1807
- isRendering = true;
1742
+ isRendering = true;
1743
1809
- // Check if this is async work or sync/expired work.
1810
- if (!isAsync) {
1811
- // Flush sync work.
1812
- let finishedWork = root.finishedWork;
1744
+ // Check if this is async work or sync/expired work.
1745
+ if (!isAsync) {
1746
+ // Flush sync work.
1747
+ let finishedWork = root.finishedWork;
1748
+ if (finishedWork !== null) {
1749
+ // This root is already complete. We can commit it.
1750
+ completeRoot(root, finishedWork, expirationTime);
1751
+ } else {
1752
+ root.finishedWork = null;
1753
+ finishedWork = renderRoot(root, expirationTime, false);
1754
if (finishedWork !== null) {
1814
- // This root is already complete. We can commit it.
1755
+ // We've completed the root. Commit it.
1756
completeRoot(root, finishedWork, expirationTime);
1816
- } else {
1817
- root.finishedWork = null;
1818
- finishedWork = renderRoot(root, expirationTime, false);
1819
- if (finishedWork !== null) {
1820
- // We've completed the root. Commit it.
1821
- completeRoot(root, finishedWork, expirationTime);
1822
- }
1757
}
1758
+ }
1759
+ } else {
1760
+ // Flush async work.
1761
+ let finishedWork = root.finishedWork;
1762
+ if (finishedWork !== null) {
1763
+ // This root is already complete. We can commit it.
1764
+ completeRoot(root, finishedWork, expirationTime);
1765
} else {
1825
- // Flush async work.
1826
- let finishedWork = root.finishedWork;
1766
+ root.finishedWork = null;
1767
+ finishedWork = renderRoot(root, expirationTime, true);
1768
if (finishedWork !== null) {
1828
- // This root is already complete. We can commit it.
1829
- completeRoot(root, finishedWork, expirationTime);
1830
- } else {
1831
- root.finishedWork = null;
1832
- finishedWork = renderRoot(root, expirationTime, true);
1833
- if (finishedWork !== null) {
1834
- // We've completed the root. Check the deadline one more time
1835
- // before committing.
1836
- if (!shouldYield()) {
1837
- // Still time left. Commit the root.
1838
- completeRoot(root, finishedWork, expirationTime);
1839
- } else {
1840
- // There's no time left. Mark this root as complete. We'll come
1841
- // back and commit it later.
1842
- root.finishedWork = finishedWork;
1843
-
1844
- if (enableProfilerTimer) {
1845
- // If we didn't finish, pause the "actual" render timer.
1846
- // We'll restart it when we resume work.
1847
- pauseActualRenderTimerIfRunning();
1848
- }
1769
+ // We've completed the root. Check the deadline one more time
1770
+ // before committing.
1771
+ if (!shouldYield()) {
1772
+ // Still time left. Commit the root.
1773
+ completeRoot(root, finishedWork, expirationTime);
1774
+ } else {
1775
+ // There's no time left. Mark this root as complete. We'll come
1776
+ // back and commit it later.
1777
+ root.finishedWork = finishedWork;
1778
+
1779
+ if (enableProfilerTimer) {
1780
+ // If we didn't finish, pause the "actual" render timer.
1781
+ // We'll restart it when we resume work.
1782
+ pauseActualRenderTimerIfRunning();
1783
}
1784
}
1785
}
1786
}
1853
-
1854
- isRendering = false;
1787
}
1788
1857
- function completeRoot(
1858
- root: FiberRoot,
1859
- finishedWork: Fiber,
1860
- expirationTime: ExpirationTime,
1861
- ): void {
1862
- // Check if there's a batch that matches this expiration time.
1863
- const firstBatch = root.firstBatch;
1864
- if (firstBatch !== null && firstBatch._expirationTime <= expirationTime) {
1865
- if (completedBatches === null) {
1866
- completedBatches = [firstBatch];
1867
- } else {
1868
- completedBatches.push(firstBatch);
1869
- }
1870
- if (firstBatch._defer) {
1871
- // This root is blocked from committing by a batch. Unschedule it until
1872
- // we receive another update.
1873
- root.finishedWork = finishedWork;
1874
- root.remainingExpirationTime = NoWork;
1875
- return;
1876
- }
1877
- }
1878
-
1879
- // Commit the root.
1880
- root.finishedWork = null;
1881
- root.remainingExpirationTime = commitRoot(finishedWork);
1882
- }
1789
+ isRendering = false;
1790
+}
1791
1884
- // When working on async work, the reconciler asks the renderer if it should
1885
- // yield execution. For DOM, we implement this with requestIdleCallback.
1886
- function shouldYield() {
1887
- if (deadline === null) {
1888
- return false;
1792
+function completeRoot(
1793
+ root: FiberRoot,
1794
+ finishedWork: Fiber,
1795
+ expirationTime: ExpirationTime,
1796
+): void {
1797
+ // Check if there's a batch that matches this expiration time.
1798
+ const firstBatch = root.firstBatch;
1799
+ if (firstBatch !== null && firstBatch._expirationTime <= expirationTime) {
1800
+ if (completedBatches === null) {
1801
+ completedBatches = [firstBatch];
1802
+ } else {
1803
+ completedBatches.push(firstBatch);
1804
}
1890
- if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
1891
- // Disregard deadline.didTimeout. Only expired work should be flushed
1892
- // during a timeout. This path is only hit for non-expired work.
1893
- return false;
1805
+ if (firstBatch._defer) {
1806
+ // This root is blocked from committing by a batch. Unschedule it until
1807
+ // we receive another update.
1808
+ root.finishedWork = finishedWork;
1809
+ root.remainingExpirationTime = NoWork;
1810
+ return;
1811
}
1895
- deadlineDidExpire = true;
1896
- return true;
1812
}
1813
1899
- function onUncaughtError(error) {
1900
- invariant(
1901
- nextFlushedRoot !== null,
1902
- 'Should be working on a root. This error is likely caused by a bug in ' +
1903
- 'React. Please file an issue.',
1904
- );
1905
- // Unschedule this root so we don't work on it again until there's
1906
- // another update.
1907
- nextFlushedRoot.remainingExpirationTime = NoWork;
1908
- if (!hasUnhandledError) {
1909
- hasUnhandledError = true;
1910
- unhandledError = error;
1911
- }
1912
- }
1814
+ // Commit the root.
1815
+ root.finishedWork = null;
1816
+ root.remainingExpirationTime = commitRoot(finishedWork);
1817
+}
1818
1914
- function onBlock(remainingExpirationTime: ExpirationTime) {
1915
- invariant(
1916
- nextFlushedRoot !== null,
1917
- 'Should be working on a root. This error is likely caused by a bug in ' +
1918
- 'React. Please file an issue.',
1919
- );
1920
- // This root was blocked. Unschedule it until there's another update.
1921
- nextFlushedRoot.remainingExpirationTime = remainingExpirationTime;
1819
+// When working on async work, the reconciler asks the renderer if it should
1820
+// yield execution. For DOM, we implement this with requestIdleCallback.
1821
+function shouldYield() {
1822
+ if (deadline === null) {
1823
+ return false;
1824
}
1923
-
1924
- // TODO: Batching should be implemented at the renderer level, not inside
1925
- // the reconciler.
1926
- function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1927
- const previousIsBatchingUpdates = isBatchingUpdates;
1928
- isBatchingUpdates = true;
1929
- try {
1930
- return fn(a);
1931
- } finally {
1932
- isBatchingUpdates = previousIsBatchingUpdates;
1933
- if (!isBatchingUpdates && !isRendering) {
1934
- performSyncWork();
1935
- }
1936
- }
1825
+ if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
1826
+ // Disregard deadline.didTimeout. Only expired work should be flushed
1827
+ // during a timeout. This path is only hit for non-expired work.
1828
+ return false;
1829
}
1830
+ deadlineDidExpire = true;
1831
+ return true;
1832
+}
1833
1939
- // TODO: Batching should be implemented at the renderer level, not inside
1940
- // the reconciler.
1941
- function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1942
- if (isBatchingUpdates && !isUnbatchingUpdates) {
1943
- isUnbatchingUpdates = true;
1944
- try {
1945
- return fn(a);
1946
- } finally {
1947
- isUnbatchingUpdates = false;
1948
- }
1949
- }
1950
- return fn(a);
1834
+function onUncaughtError(error: mixed) {
1835
+ invariant(
1836
+ nextFlushedRoot !== null,
1837
+ 'Should be working on a root. This error is likely caused by a bug in ' +
1838
+ 'React. Please file an issue.',
1839
+ );
1840
+ // Unschedule this root so we don't work on it again until there's
1841
+ // another update.
1842
+ nextFlushedRoot.remainingExpirationTime = NoWork;
1843
+ if (!hasUnhandledError) {
1844
+ hasUnhandledError = true;
1845
+ unhandledError = error;
1846
}
1847
+}
1848
1953
- // TODO: Batching should be implemented at the renderer level, not within
1954
- // the reconciler.
1955
- function flushSync<A, R>(fn: (a: A) => R, a: A): R {
1956
- invariant(
1957
- !isRendering,
1958
- 'flushSync was called from inside a lifecycle method. It cannot be ' +
1959
- 'called when React is already rendering.',
1960
- );
1961
- const previousIsBatchingUpdates = isBatchingUpdates;
1962
- isBatchingUpdates = true;
1963
- try {
1964
- return syncUpdates(fn, a);
1965
- } finally {
1966
- isBatchingUpdates = previousIsBatchingUpdates;
1849
+function onBlock(remainingExpirationTime: ExpirationTime) {
1850
+ invariant(
1851
+ nextFlushedRoot !== null,
1852
+ 'Should be working on a root. This error is likely caused by a bug in ' +
1853
+ 'React. Please file an issue.',
1854
+ );
1855
+ // This root was blocked. Unschedule it until there's another update.
1856
+ nextFlushedRoot.remainingExpirationTime = remainingExpirationTime;
1857
+}
1858
+
1859
+// TODO: Batching should be implemented at the renderer level, not inside
1860
+// the reconciler.
1861
+function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1862
+ const previousIsBatchingUpdates = isBatchingUpdates;
1863
+ isBatchingUpdates = true;
1864
+ try {
1865
+ return fn(a);
1866
+ } finally {
1867
+ isBatchingUpdates = previousIsBatchingUpdates;
1868
+ if (!isBatchingUpdates && !isRendering) {
1869
performSyncWork();
1870
}
1871
}
1872
+}
1873
1971
- function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
1972
- if (isBatchingInteractiveUpdates) {
1973
- return fn(a, b);
1974
- }
1975
- // If there are any pending interactive updates, synchronously flush them.
1976
- // This needs to happen before we read any handlers, because the effect of
1977
- // the previous event may influence which handlers are called during
1978
- // this event.
1979
- if (
1980
- !isBatchingUpdates &&
1981
- !isRendering &&
1982
- lowestPendingInteractiveExpirationTime !== NoWork
1983
- ) {
1984
- // Synchronously flush pending interactive updates.
1985
- performWork(lowestPendingInteractiveExpirationTime, false, null);
1986
- lowestPendingInteractiveExpirationTime = NoWork;
1987
- }
1988
- const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1989
- const previousIsBatchingUpdates = isBatchingUpdates;
1990
- isBatchingInteractiveUpdates = true;
1991
- isBatchingUpdates = true;
1874
+// TODO: Batching should be implemented at the renderer level, not inside
1875
+// the reconciler.
1876
+function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1877
+ if (isBatchingUpdates && !isUnbatchingUpdates) {
1878
+ isUnbatchingUpdates = true;
1879
try {
1993
- return fn(a, b);
1880
+ return fn(a);
1881
} finally {
1995
- isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1996
- isBatchingUpdates = previousIsBatchingUpdates;
1997
- if (!isBatchingUpdates && !isRendering) {
1998
- performSyncWork();
1999
- }
1882
+ isUnbatchingUpdates = false;
1883
}
1884
}
1885
+ return fn(a);
1886
+}
1887
2003
- function flushInteractiveUpdates() {
2004
- if (!isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
2005
- // Synchronously flush pending interactive updates.
2006
- performWork(lowestPendingInteractiveExpirationTime, false, null);
2007
- lowestPendingInteractiveExpirationTime = NoWork;
2008
- }
1888
+// TODO: Batching should be implemented at the renderer level, not within
1889
+// the reconciler.
1890
+function flushSync<A, R>(fn: (a: A) => R, a: A): R {
1891
+ invariant(
1892
+ !isRendering,
1893
+ 'flushSync was called from inside a lifecycle method. It cannot be ' +
1894
+ 'called when React is already rendering.',
1895
+ );
1896
+ const previousIsBatchingUpdates = isBatchingUpdates;
1897
+ isBatchingUpdates = true;
1898
+ try {
1899
+ return syncUpdates(fn, a);
1900
+ } finally {
1901
+ isBatchingUpdates = previousIsBatchingUpdates;
1902
+ performSyncWork();
1903
}
1904
+}
1905
2011
- function flushControlled(fn: () => mixed): void {
2012
- const previousIsBatchingUpdates = isBatchingUpdates;
2013
- isBatchingUpdates = true;
2014
- try {
2015
- syncUpdates(fn);
2016
- } finally {
2017
- isBatchingUpdates = previousIsBatchingUpdates;
2018
- if (!isBatchingUpdates && !isRendering) {
2019
- performWork(Sync, false, null);
2020
- }
1906
+function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
1907
+ if (isBatchingInteractiveUpdates) {
1908
+ return fn(a, b);
1909
+ }
1910
+ // If there are any pending interactive updates, synchronously flush them.
1911
+ // This needs to happen before we read any handlers, because the effect of
1912
+ // the previous event may influence which handlers are called during
1913
+ // this event.
1914
+ if (
1915
+ !isBatchingUpdates &&
1916
+ !isRendering &&
1917
+ lowestPendingInteractiveExpirationTime !== NoWork
1918
+ ) {
1919
+ // Synchronously flush pending interactive updates.
1920
+ performWork(lowestPendingInteractiveExpirationTime, false, null);
1921
+ lowestPendingInteractiveExpirationTime = NoWork;
1922
+ }
1923
+ const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1924
+ const previousIsBatchingUpdates = isBatchingUpdates;
1925
+ isBatchingInteractiveUpdates = true;
1926
+ isBatchingUpdates = true;
1927
+ try {
1928
+ return fn(a, b);
1929
+ } finally {
1930
+ isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1931
+ isBatchingUpdates = previousIsBatchingUpdates;
1932
+ if (!isBatchingUpdates && !isRendering) {
1933
+ performSyncWork();
1934
}
1935
}
1936
+}
1937
2024
- return {
2025
- recalculateCurrentTime,
2026
- computeExpirationForFiber,
2027
- scheduleWork,
2028
- requestWork,
2029
- flushRoot,
2030
- batchedUpdates,
2031
- unbatchedUpdates,
2032
- flushSync,
2033
- flushControlled,
2034
- deferredUpdates,
2035
- syncUpdates,
2036
- interactiveUpdates,
2037
- flushInteractiveUpdates,
2038
- computeUniqueAsyncExpiration,
2039
- legacyContext,
2040
- };
1938
+function flushInteractiveUpdates() {
1939
+ if (!isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
1940
+ // Synchronously flush pending interactive updates.
1941
+ performWork(lowestPendingInteractiveExpirationTime, false, null);
1942
+ lowestPendingInteractiveExpirationTime = NoWork;
1943
+ }
1944
}
1945
+
1946
+function flushControlled(fn: () => mixed): void {
1947
+ const previousIsBatchingUpdates = isBatchingUpdates;
1948
+ isBatchingUpdates = true;
1949
+ try {
1950
+ syncUpdates(fn);
1951
+ } finally {
1952
+ isBatchingUpdates = previousIsBatchingUpdates;
1953
+ if (!isBatchingUpdates && !isRendering) {
1954
+ performWork(Sync, false, null);
1955
+ }
1956
+ }
1957
+}
1958
+
1959
+export {
1960
+ recalculateCurrentTime,
1961
+ computeExpirationForFiber,
1962
+ captureCommitPhaseError,
1963
+ onUncaughtError,
1964
+ suspendRoot,
1965
+ retrySuspendedRoot,
1966
+ markLegacyErrorBoundaryAsFailed,
1967
+ isAlreadyFailedLegacyErrorBoundary,
1968
+ scheduleWork,
1969
+ requestWork,
1970
+ flushRoot,
1971
+ batchedUpdates,
1972
+ unbatchedUpdates,
1973
+ flushSync,
1974
+ flushControlled,
1975
+ deferredUpdates,
1976
+ syncUpdates,
1977
+ interactiveUpdates,
1978
+ flushInteractiveUpdates,
1979
+ computeUniqueAsyncExpiration,
1980
+};