Rename ReactFiberScheduler -> ReactFiberWorkLoop and extract throwException from Unwind (#15725)
* Rename ReactFiberScheduler to ReactFiberWorkLoop The scheduling part is mostly extracted out to the scheduler package. What's remaining is mostly around the loop around each section of work. I name it something with Work in it because it's very related to the BeginWork, CompleteWork and UnwindWork sections. * Extract throwException from UnwindWork Our throwing works more like algebraic effects in that it's a separate phase where we find a handler and we later unwind.
Sebastian Markbåge committed
May 23, 2019 at 14:24 UTC
8af1f879299e88f9e370c1f01d90efaeefcefd27
17 files changed
+403
-383
packages/react-dom/src/test-utils/ReactTestUtils.js
+1
-1
@@ -4,7 +4,7 @@
4
* This source code is licensed under the MIT license found in the
5
* LICENSE file in the root directory of this source tree.
6
*/
7
-import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
7
+import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
8
9
import React from 'react';
10
import ReactDOM from 'react-dom';
packages/react-dom/src/test-utils/ReactTestUtilsAct.js
+1
-1
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
10
+import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
11
12
import warningWithoutStack from 'shared/warningWithoutStack';
13
import ReactDOM from 'react-dom';
packages/react-noop-renderer/src/createReactNoop.js
+1
-1
@@ -14,7 +14,7 @@
14
* environment.
15
*/
16
17
-import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
17
+import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
18
import type {Fiber} from 'react-reconciler/src/ReactFiber';
19
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
20
import type {ReactNodeList} from 'shared/ReactTypes';
packages/react-reconciler/src/ReactFiberBeginWork.js
+1
-1
@@ -160,7 +160,7 @@ import {
160
createWorkInProgress,
161
isSimpleFunctionComponent,
162
} from './ReactFiber';
163
-import {requestCurrentTime, retryTimedOutBoundary} from './ReactFiberScheduler';
163
+import {requestCurrentTime, retryTimedOutBoundary} from './ReactFiberWorkLoop';
164
165
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
166
packages/react-reconciler/src/ReactFiberClassComponent.js
+1
-1
@@ -53,7 +53,7 @@ import {
53
computeExpirationForFiber,
54
scheduleWork,
55
flushPassiveEffects,
56
-} from './ReactFiberScheduler';
56
+} from './ReactFiberWorkLoop';
57
import {revertPassiveEffectsChange} from 'shared/ReactFeatureFlags';
58
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
59
packages/react-reconciler/src/ReactFiberCommitWork.js
+2
-2
@@ -21,7 +21,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
21
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
22
import type {SuspenseState} from './ReactFiberSuspenseComponent';
23
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
24
-import type {Thenable} from './ReactFiberScheduler';
24
+import type {Thenable} from './ReactFiberWorkLoop';
25
26
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
27
import {
@@ -104,7 +104,7 @@ import {
104
captureCommitPhaseError,
105
requestCurrentTime,
106
resolveRetryThenable,
107
-} from './ReactFiberScheduler';
107
+} from './ReactFiberWorkLoop';
108
import {
109
NoEffect as NoHookEffect,
110
UnmountSnapshot,
packages/react-reconciler/src/ReactFiberCompleteWork.js
+1
-1
@@ -104,7 +104,7 @@ import {
104
markRenderEventTimeAndConfig,
105
renderDidSuspend,
106
renderDidSuspendDelayIfPossible,
107
-} from './ReactFiberScheduler';
107
+} from './ReactFiberWorkLoop';
108
import {getEventComponentHostChildrenCount} from './ReactFiberEvents';
109
import getComponentName from 'shared/getComponentName';
110
import warning from 'shared/warning';
packages/react-reconciler/src/ReactFiberDevToolsHook.js
+1
-1
@@ -8,7 +8,7 @@
8
*/
9
10
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
11
-import {requestCurrentTime} from './ReactFiberScheduler';
11
+import {requestCurrentTime} from './ReactFiberWorkLoop';
12
import {inferPriorityFromExpirationTime} from './ReactFiberExpirationTime';
13
14
import type {Fiber} from './ReactFiber';
packages/react-reconciler/src/ReactFiberHooks.js
+1
-1
@@ -36,7 +36,7 @@ import {
36
requestCurrentTime,
37
warnIfNotCurrentlyActingUpdatesInDev,
38
markRenderEventTimeAndConfig,
39
-} from './ReactFiberScheduler';
39
+} from './ReactFiberWorkLoop';
40
41
import invariant from 'shared/invariant';
42
import warning from 'shared/warning';
packages/react-reconciler/src/ReactFiberHotReloading.js
+1
-1
@@ -15,7 +15,7 @@ import {
15
flushSync,
16
scheduleWork,
17
flushPassiveEffects,
18
-} from './ReactFiberScheduler';
18
+} from './ReactFiberWorkLoop';
19
import {Sync} from './ReactFiberExpirationTime';
20
import {
21
FunctionComponent,
packages/react-reconciler/src/ReactFiberReconciler.js
+1
-1
@@ -56,7 +56,7 @@ import {
56
discreteUpdates,
57
flushDiscreteUpdates,
58
flushPassiveEffects,
59
-} from './ReactFiberScheduler';
59
+} from './ReactFiberWorkLoop';
60
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
61
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
62
import {
packages/react-reconciler/src/ReactFiberRoot.js
+1
-1
@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
11
import type {ExpirationTime} from './ReactFiberExpirationTime';
12
import type {RootTag} from 'shared/ReactRootTags';
13
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
14
-import type {Thenable} from './ReactFiberScheduler';
14
+import type {Thenable} from './ReactFiberWorkLoop';
15
import type {Interaction} from 'scheduler/src/Tracing';
16
17
import {noTimeout} from './ReactFiberHostConfig';
packages/react-reconciler/src/ReactFiberThrow.js
new
+383
@@ -0,0 +1,383 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactFiber';
11
+import type {FiberRoot} from './ReactFiberRoot';
12
+import type {ExpirationTime} from './ReactFiberExpirationTime';
13
+import type {CapturedValue} from './ReactCapturedValue';
14
+import type {Update} from './ReactUpdateQueue';
15
+import type {Thenable} from './ReactFiberWorkLoop';
16
+import type {SuspenseContext} from './ReactFiberSuspenseContext';
17
+
18
+import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
19
+import getComponentName from 'shared/getComponentName';
20
+import warningWithoutStack from 'shared/warningWithoutStack';
21
+import {
22
+ ClassComponent,
23
+ HostRoot,
24
+ SuspenseComponent,
25
+ DehydratedSuspenseComponent,
26
+ IncompleteClassComponent,
27
+} from 'shared/ReactWorkTags';
28
+import {
29
+ DidCapture,
30
+ Incomplete,
31
+ NoEffect,
32
+ ShouldCapture,
33
+ LifecycleEffectMask,
34
+} from 'shared/ReactSideEffectTags';
35
+import {
36
+ enableSchedulerTracing,
37
+ enableSuspenseServerRenderer,
38
+} from 'shared/ReactFeatureFlags';
39
+import {NoMode, BatchedMode} from './ReactTypeOfMode';
40
+import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent';
41
+
42
+import {createCapturedValue} from './ReactCapturedValue';
43
+import {
44
+ enqueueCapturedUpdate,
45
+ createUpdate,
46
+ CaptureUpdate,
47
+ ForceUpdate,
48
+ enqueueUpdate,
49
+} from './ReactUpdateQueue';
50
+import {logError} from './ReactFiberCommitWork';
51
+import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
52
+import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading';
53
+import {
54
+ suspenseStackCursor,
55
+ InvisibleParentSuspenseContext,
56
+ hasSuspenseContext,
57
+} from './ReactFiberSuspenseContext';
58
+import {
59
+ renderDidError,
60
+ onUncaughtError,
61
+ markLegacyErrorBoundaryAsFailed,
62
+ isAlreadyFailedLegacyErrorBoundary,
63
+ pingSuspendedRoot,
64
+ resolveRetryThenable,
65
+ checkForWrongSuspensePriorityInDEV,
66
+} from './ReactFiberWorkLoop';
67
+
68
+import invariant from 'shared/invariant';
69
+
70
+import {Sync} from './ReactFiberExpirationTime';
71
+
72
+const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
73
+const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
74
+
75
+function createRootErrorUpdate(
76
+ fiber: Fiber,
77
+ errorInfo: CapturedValue<mixed>,
78
+ expirationTime: ExpirationTime,
79
+): Update<mixed> {
80
+ const update = createUpdate(expirationTime, null);
81
+ // Unmount the root by rendering null.
82
+ update.tag = CaptureUpdate;
83
+ // Caution: React DevTools currently depends on this property
84
+ // being called "element".
85
+ update.payload = {element: null};
86
+ const error = errorInfo.value;
87
+ update.callback = () => {
88
+ onUncaughtError(error);
89
+ logError(fiber, errorInfo);
90
+ };
91
+ return update;
92
+}
93
+
94
+function createClassErrorUpdate(
95
+ fiber: Fiber,
96
+ errorInfo: CapturedValue<mixed>,
97
+ expirationTime: ExpirationTime,
98
+): Update<mixed> {
99
+ const update = createUpdate(expirationTime, null);
100
+ update.tag = CaptureUpdate;
101
+ const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
102
+ if (typeof getDerivedStateFromError === 'function') {
103
+ const error = errorInfo.value;
104
+ update.payload = () => {
105
+ return getDerivedStateFromError(error);
106
+ };
107
+ }
108
+
109
+ const inst = fiber.stateNode;
110
+ if (inst !== null && typeof inst.componentDidCatch === 'function') {
111
+ update.callback = function callback() {
112
+ if (__DEV__) {
113
+ markFailedErrorBoundaryForHotReloading(fiber);
114
+ }
115
+ if (typeof getDerivedStateFromError !== 'function') {
116
+ // To preserve the preexisting retry behavior of error boundaries,
117
+ // we keep track of which ones already failed during this batch.
118
+ // This gets reset before we yield back to the browser.
119
+ // TODO: Warn in strict mode if getDerivedStateFromError is
120
+ // not defined.
121
+ markLegacyErrorBoundaryAsFailed(this);
122
+ }
123
+ const error = errorInfo.value;
124
+ const stack = errorInfo.stack;
125
+ logError(fiber, errorInfo);
126
+ this.componentDidCatch(error, {
127
+ componentStack: stack !== null ? stack : '',
128
+ });
129
+ if (__DEV__) {
130
+ if (typeof getDerivedStateFromError !== 'function') {
131
+ // If componentDidCatch is the only error boundary method defined,
132
+ // then it needs to call setState to recover from errors.
133
+ // If no state update is scheduled then the boundary will swallow the error.
134
+ warningWithoutStack(
135
+ fiber.expirationTime === Sync,
136
+ '%s: Error boundaries should implement getDerivedStateFromError(). ' +
137
+ 'In that method, return a state update to display an error message or fallback UI.',
138
+ getComponentName(fiber.type) || 'Unknown',
139
+ );
140
+ }
141
+ }
142
+ };
143
+ } else if (__DEV__) {
144
+ update.callback = () => {
145
+ markFailedErrorBoundaryForHotReloading(fiber);
146
+ };
147
+ }
148
+ return update;
149
+}
150
+
151
+function attachPingListener(
152
+ root: FiberRoot,
153
+ renderExpirationTime: ExpirationTime,
154
+ thenable: Thenable,
155
+) {
156
+ // Attach a listener to the promise to "ping" the root and retry. But
157
+ // only if one does not already exist for the current render expiration
158
+ // time (which acts like a "thread ID" here).
159
+ let pingCache = root.pingCache;
160
+ let threadIDs;
161
+ if (pingCache === null) {
162
+ pingCache = root.pingCache = new PossiblyWeakMap();
163
+ threadIDs = new Set();
164
+ pingCache.set(thenable, threadIDs);
165
+ } else {
166
+ threadIDs = pingCache.get(thenable);
167
+ if (threadIDs === undefined) {
168
+ threadIDs = new Set();
169
+ pingCache.set(thenable, threadIDs);
170
+ }
171
+ }
172
+ if (!threadIDs.has(renderExpirationTime)) {
173
+ // Memoize using the thread ID to prevent redundant listeners.
174
+ threadIDs.add(renderExpirationTime);
175
+ let ping = pingSuspendedRoot.bind(
176
+ null,
177
+ root,
178
+ thenable,
179
+ renderExpirationTime,
180
+ );
181
+ if (enableSchedulerTracing) {
182
+ ping = Schedule_tracing_wrap(ping);
183
+ }
184
+ thenable.then(ping, ping);
185
+ }
186
+}
187
+
188
+function throwException(
189
+ root: FiberRoot,
190
+ returnFiber: Fiber,
191
+ sourceFiber: Fiber,
192
+ value: mixed,
193
+ renderExpirationTime: ExpirationTime,
194
+) {
195
+ // The source fiber did not complete.
196
+ sourceFiber.effectTag |= Incomplete;
197
+ // Its effect list is no longer valid.
198
+ sourceFiber.firstEffect = sourceFiber.lastEffect = null;
199
+
200
+ if (
201
+ value !== null &&
202
+ typeof value === 'object' &&
203
+ typeof value.then === 'function'
204
+ ) {
205
+ // This is a thenable.
206
+ const thenable: Thenable = (value: any);
207
+
208
+ checkForWrongSuspensePriorityInDEV(sourceFiber);
209
+
210
+ let hasInvisibleParentBoundary = hasSuspenseContext(
211
+ suspenseStackCursor.current,
212
+ (InvisibleParentSuspenseContext: SuspenseContext),
213
+ );
214
+
215
+ // Schedule the nearest Suspense to re-render the timed out view.
216
+ let workInProgress = returnFiber;
217
+ do {
218
+ if (
219
+ workInProgress.tag === SuspenseComponent &&
220
+ shouldCaptureSuspense(workInProgress, hasInvisibleParentBoundary)
221
+ ) {
222
+ // Found the nearest boundary.
223
+
224
+ // Stash the promise on the boundary fiber. If the boundary times out, we'll
225
+ // attach another listener to flip the boundary back to its normal state.
226
+ const thenables: Set<Thenable> = (workInProgress.updateQueue: any);
227
+ if (thenables === null) {
228
+ const updateQueue = (new Set(): any);
229
+ updateQueue.add(thenable);
230
+ workInProgress.updateQueue = updateQueue;
231
+ } else {
232
+ thenables.add(thenable);
233
+ }
234
+
235
+ // If the boundary is outside of batched mode, we should *not*
236
+ // suspend the commit. Pretend as if the suspended component rendered
237
+ // null and keep rendering. In the commit phase, we'll schedule a
238
+ // subsequent synchronous update to re-render the Suspense.
239
+ //
240
+ // Note: It doesn't matter whether the component that suspended was
241
+ // inside a batched mode tree. If the Suspense is outside of it, we
242
+ // should *not* suspend the commit.
243
+ if ((workInProgress.mode & BatchedMode) === NoMode) {
244
+ workInProgress.effectTag |= DidCapture;
245
+
246
+ // We're going to commit this fiber even though it didn't complete.
247
+ // But we shouldn't call any lifecycle methods or callbacks. Remove
248
+ // all lifecycle effect tags.
249
+ sourceFiber.effectTag &= ~(LifecycleEffectMask | Incomplete);
250
+
251
+ if (sourceFiber.tag === ClassComponent) {
252
+ const currentSourceFiber = sourceFiber.alternate;
253
+ if (currentSourceFiber === null) {
254
+ // This is a new mount. Change the tag so it's not mistaken for a
255
+ // completed class component. For example, we should not call
256
+ // componentWillUnmount if it is deleted.
257
+ sourceFiber.tag = IncompleteClassComponent;
258
+ } else {
259
+ // When we try rendering again, we should not reuse the current fiber,
260
+ // since it's known to be in an inconsistent state. Use a force updte to
261
+ // prevent a bail out.
262
+ const update = createUpdate(Sync, null);
263
+ update.tag = ForceUpdate;
264
+ enqueueUpdate(sourceFiber, update);
265
+ }
266
+ }
267
+
268
+ // The source fiber did not complete. Mark it with Sync priority to
269
+ // indicate that it still has pending work.
270
+ sourceFiber.expirationTime = Sync;
271
+
272
+ // Exit without suspending.
273
+ return;
274
+ }
275
+
276
+ // Confirmed that the boundary is in a concurrent mode tree. Continue
277
+ // with the normal suspend path.
278
+
279
+ attachPingListener(root, renderExpirationTime, thenable);
280
+
281
+ workInProgress.effectTag |= ShouldCapture;
282
+ workInProgress.expirationTime = renderExpirationTime;
283
+
284
+ return;
285
+ } else if (
286
+ enableSuspenseServerRenderer &&
287
+ workInProgress.tag === DehydratedSuspenseComponent
288
+ ) {
289
+ attachPingListener(root, renderExpirationTime, thenable);
290
+
291
+ // Since we already have a current fiber, we can eagerly add a retry listener.
292
+ let retryCache = workInProgress.memoizedState;
293
+ if (retryCache === null) {
294
+ retryCache = workInProgress.memoizedState = new PossiblyWeakSet();
295
+ const current = workInProgress.alternate;
296
+ invariant(
297
+ current,
298
+ 'A dehydrated suspense boundary must commit before trying to render. ' +
299
+ 'This is probably a bug in React.',
300
+ );
301
+ current.memoizedState = retryCache;
302
+ }
303
+ // Memoize using the boundary fiber to prevent redundant listeners.
304
+ if (!retryCache.has(thenable)) {
305
+ retryCache.add(thenable);
306
+ let retry = resolveRetryThenable.bind(null, workInProgress, thenable);
307
+ if (enableSchedulerTracing) {
308
+ retry = Schedule_tracing_wrap(retry);
309
+ }
310
+ thenable.then(retry, retry);
311
+ }
312
+ workInProgress.effectTag |= ShouldCapture;
313
+ workInProgress.expirationTime = renderExpirationTime;
314
+ return;
315
+ }
316
+ // This boundary already captured during this render. Continue to the next
317
+ // boundary.
318
+ workInProgress = workInProgress.return;
319
+ } while (workInProgress !== null);
320
+ // No boundary was found. Fallthrough to error mode.
321
+ // TODO: Use invariant so the message is stripped in prod?
322
+ value = new Error(
323
+ (getComponentName(sourceFiber.type) || 'A React component') +
324
+ ' suspended while rendering, but no fallback UI was specified.\n' +
325
+ '\n' +
326
+ 'Add a <Suspense fallback=...> component higher in the tree to ' +
327
+ 'provide a loading indicator or placeholder to display.' +
328
+ getStackByFiberInDevAndProd(sourceFiber),
329
+ );
330
+ }
331
+
332
+ // We didn't find a boundary that could handle this type of exception. Start
333
+ // over and traverse parent path again, this time treating the exception
334
+ // as an error.
335
+ renderDidError();
336
+ value = createCapturedValue(value, sourceFiber);
337
+ let workInProgress = returnFiber;
338
+ do {
339
+ switch (workInProgress.tag) {
340
+ case HostRoot: {
341
+ const errorInfo = value;
342
+ workInProgress.effectTag |= ShouldCapture;
343
+ workInProgress.expirationTime = renderExpirationTime;
344
+ const update = createRootErrorUpdate(
345
+ workInProgress,
346
+ errorInfo,
347
+ renderExpirationTime,
348
+ );
349
+ enqueueCapturedUpdate(workInProgress, update);
350
+ return;
351
+ }
352
+ case ClassComponent:
353
+ // Capture and retry
354
+ const errorInfo = value;
355
+ const ctor = workInProgress.type;
356
+ const instance = workInProgress.stateNode;
357
+ if (
358
+ (workInProgress.effectTag & DidCapture) === NoEffect &&
359
+ (typeof ctor.getDerivedStateFromError === 'function' ||
360
+ (instance !== null &&
361
+ typeof instance.componentDidCatch === 'function' &&
362
+ !isAlreadyFailedLegacyErrorBoundary(instance)))
363
+ ) {
364
+ workInProgress.effectTag |= ShouldCapture;
365
+ workInProgress.expirationTime = renderExpirationTime;
366
+ // Schedule the error boundary to re-render using updated state
367
+ const update = createClassErrorUpdate(
368
+ workInProgress,
369
+ errorInfo,
370
+ renderExpirationTime,
371
+ );
372
+ enqueueCapturedUpdate(workInProgress, update);
373
+ return;
374
+ }
375
+ break;
376
+ default:
377
+ break;
378
+ }
379
+ workInProgress = workInProgress.return;
380
+ } while (workInProgress !== null);
381
+}
382
+
383
+export {throwException, createRootErrorUpdate, createClassErrorUpdate};
packages/react-reconciler/src/ReactFiberUnwindWork.js
+3
-365
@@ -8,16 +8,8 @@
8
*/
9
10
import type {Fiber} from './ReactFiber';
11
-import type {FiberRoot} from './ReactFiberRoot';
11
import type {ExpirationTime} from './ReactFiberExpirationTime';
13
-import type {CapturedValue} from './ReactCapturedValue';
14
-import type {Update} from './ReactUpdateQueue';
15
-import type {Thenable} from './ReactFiberScheduler';
16
-import type {SuspenseContext} from './ReactFiberSuspenseContext';
12
18
-import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
19
-import getComponentName from 'shared/getComponentName';
20
-import warningWithoutStack from 'shared/warningWithoutStack';
13
import {
14
ClassComponent,
15
HostRoot,
@@ -26,374 +18,26 @@ import {
18
ContextProvider,
19
SuspenseComponent,
20
DehydratedSuspenseComponent,
29
- IncompleteClassComponent,
21
EventComponent,
22
EventTarget,
23
} from 'shared/ReactWorkTags';
24
+import {DidCapture, NoEffect, ShouldCapture} from 'shared/ReactSideEffectTags';
25
import {
34
- DidCapture,
35
- Incomplete,
36
- NoEffect,
37
- ShouldCapture,
38
- LifecycleEffectMask,
39
-} from 'shared/ReactSideEffectTags';
40
-import {
41
- enableSchedulerTracing,
26
enableSuspenseServerRenderer,
27
enableEventAPI,
28
} from 'shared/ReactFeatureFlags';
45
-import {NoMode, BatchedMode} from './ReactTypeOfMode';
46
-import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent';
29
48
-import {createCapturedValue} from './ReactCapturedValue';
49
-import {
50
- enqueueCapturedUpdate,
51
- createUpdate,
52
- CaptureUpdate,
53
- ForceUpdate,
54
- enqueueUpdate,
55
-} from './ReactUpdateQueue';
56
-import {logError} from './ReactFiberCommitWork';
57
-import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
30
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
59
-import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading';
60
-import {
61
- suspenseStackCursor,
62
- InvisibleParentSuspenseContext,
63
- hasSuspenseContext,
64
- popSuspenseContext,
65
-} from './ReactFiberSuspenseContext';
31
+import {popSuspenseContext} from './ReactFiberSuspenseContext';
32
import {
33
isContextProvider as isLegacyContextProvider,
34
popContext as popLegacyContext,
35
popTopLevelContextObject as popTopLevelLegacyContextObject,
36
} from './ReactFiberContext';
37
import {popProvider} from './ReactFiberNewContext';
72
-import {
73
- renderDidError,
74
- onUncaughtError,
75
- markLegacyErrorBoundaryAsFailed,
76
- isAlreadyFailedLegacyErrorBoundary,
77
- pingSuspendedRoot,
78
- resolveRetryThenable,
79
- checkForWrongSuspensePriorityInDEV,
80
-} from './ReactFiberScheduler';
38
39
import invariant from 'shared/invariant';
40
84
-import {Sync} from './ReactFiberExpirationTime';
85
-
86
-const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
87
-const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
88
-
89
-function createRootErrorUpdate(
90
- fiber: Fiber,
91
- errorInfo: CapturedValue<mixed>,
92
- expirationTime: ExpirationTime,
93
-): Update<mixed> {
94
- const update = createUpdate(expirationTime, null);
95
- // Unmount the root by rendering null.
96
- update.tag = CaptureUpdate;
97
- // Caution: React DevTools currently depends on this property
98
- // being called "element".
99
- update.payload = {element: null};
100
- const error = errorInfo.value;
101
- update.callback = () => {
102
- onUncaughtError(error);
103
- logError(fiber, errorInfo);
104
- };
105
- return update;
106
-}
107
-
108
-function createClassErrorUpdate(
109
- fiber: Fiber,
110
- errorInfo: CapturedValue<mixed>,
111
- expirationTime: ExpirationTime,
112
-): Update<mixed> {
113
- const update = createUpdate(expirationTime, null);
114
- update.tag = CaptureUpdate;
115
- const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
116
- if (typeof getDerivedStateFromError === 'function') {
117
- const error = errorInfo.value;
118
- update.payload = () => {
119
- return getDerivedStateFromError(error);
120
- };
121
- }
122
-
123
- const inst = fiber.stateNode;
124
- if (inst !== null && typeof inst.componentDidCatch === 'function') {
125
- update.callback = function callback() {
126
- if (__DEV__) {
127
- markFailedErrorBoundaryForHotReloading(fiber);
128
- }
129
- if (typeof getDerivedStateFromError !== 'function') {
130
- // To preserve the preexisting retry behavior of error boundaries,
131
- // we keep track of which ones already failed during this batch.
132
- // This gets reset before we yield back to the browser.
133
- // TODO: Warn in strict mode if getDerivedStateFromError is
134
- // not defined.
135
- markLegacyErrorBoundaryAsFailed(this);
136
- }
137
- const error = errorInfo.value;
138
- const stack = errorInfo.stack;
139
- logError(fiber, errorInfo);
140
- this.componentDidCatch(error, {
141
- componentStack: stack !== null ? stack : '',
142
- });
143
- if (__DEV__) {
144
- if (typeof getDerivedStateFromError !== 'function') {
145
- // If componentDidCatch is the only error boundary method defined,
146
- // then it needs to call setState to recover from errors.
147
- // If no state update is scheduled then the boundary will swallow the error.
148
- warningWithoutStack(
149
- fiber.expirationTime === Sync,
150
- '%s: Error boundaries should implement getDerivedStateFromError(). ' +
151
- 'In that method, return a state update to display an error message or fallback UI.',
152
- getComponentName(fiber.type) || 'Unknown',
153
- );
154
- }
155
- }
156
- };
157
- } else if (__DEV__) {
158
- update.callback = () => {
159
- markFailedErrorBoundaryForHotReloading(fiber);
160
- };
161
- }
162
- return update;
163
-}
164
-
165
-function attachPingListener(
166
- root: FiberRoot,
167
- renderExpirationTime: ExpirationTime,
168
- thenable: Thenable,
169
-) {
170
- // Attach a listener to the promise to "ping" the root and retry. But
171
- // only if one does not already exist for the current render expiration
172
- // time (which acts like a "thread ID" here).
173
- let pingCache = root.pingCache;
174
- let threadIDs;
175
- if (pingCache === null) {
176
- pingCache = root.pingCache = new PossiblyWeakMap();
177
- threadIDs = new Set();
178
- pingCache.set(thenable, threadIDs);
179
- } else {
180
- threadIDs = pingCache.get(thenable);
181
- if (threadIDs === undefined) {
182
- threadIDs = new Set();
183
- pingCache.set(thenable, threadIDs);
184
- }
185
- }
186
- if (!threadIDs.has(renderExpirationTime)) {
187
- // Memoize using the thread ID to prevent redundant listeners.
188
- threadIDs.add(renderExpirationTime);
189
- let ping = pingSuspendedRoot.bind(
190
- null,
191
- root,
192
- thenable,
193
- renderExpirationTime,
194
- );
195
- if (enableSchedulerTracing) {
196
- ping = Schedule_tracing_wrap(ping);
197
- }
198
- thenable.then(ping, ping);
199
- }
200
-}
201
-
202
-function throwException(
203
- root: FiberRoot,
204
- returnFiber: Fiber,
205
- sourceFiber: Fiber,
206
- value: mixed,
207
- renderExpirationTime: ExpirationTime,
208
-) {
209
- // The source fiber did not complete.
210
- sourceFiber.effectTag |= Incomplete;
211
- // Its effect list is no longer valid.
212
- sourceFiber.firstEffect = sourceFiber.lastEffect = null;
213
-
214
- if (
215
- value !== null &&
216
- typeof value === 'object' &&
217
- typeof value.then === 'function'
218
- ) {
219
- // This is a thenable.
220
- const thenable: Thenable = (value: any);
221
-
222
- checkForWrongSuspensePriorityInDEV(sourceFiber);
223
-
224
- let hasInvisibleParentBoundary = hasSuspenseContext(
225
- suspenseStackCursor.current,
226
- (InvisibleParentSuspenseContext: SuspenseContext),
227
- );
228
-
229
- // Schedule the nearest Suspense to re-render the timed out view.
230
- let workInProgress = returnFiber;
231
- do {
232
- if (
233
- workInProgress.tag === SuspenseComponent &&
234
- shouldCaptureSuspense(workInProgress, hasInvisibleParentBoundary)
235
- ) {
236
- // Found the nearest boundary.
237
-
238
- // Stash the promise on the boundary fiber. If the boundary times out, we'll
239
- // attach another listener to flip the boundary back to its normal state.
240
- const thenables: Set<Thenable> = (workInProgress.updateQueue: any);
241
- if (thenables === null) {
242
- const updateQueue = (new Set(): any);
243
- updateQueue.add(thenable);
244
- workInProgress.updateQueue = updateQueue;
245
- } else {
246
- thenables.add(thenable);
247
- }
248
-
249
- // If the boundary is outside of batched mode, we should *not*
250
- // suspend the commit. Pretend as if the suspended component rendered
251
- // null and keep rendering. In the commit phase, we'll schedule a
252
- // subsequent synchronous update to re-render the Suspense.
253
- //
254
- // Note: It doesn't matter whether the component that suspended was
255
- // inside a batched mode tree. If the Suspense is outside of it, we
256
- // should *not* suspend the commit.
257
- if ((workInProgress.mode & BatchedMode) === NoMode) {
258
- workInProgress.effectTag |= DidCapture;
259
-
260
- // We're going to commit this fiber even though it didn't complete.
261
- // But we shouldn't call any lifecycle methods or callbacks. Remove
262
- // all lifecycle effect tags.
263
- sourceFiber.effectTag &= ~(LifecycleEffectMask | Incomplete);
264
-
265
- if (sourceFiber.tag === ClassComponent) {
266
- const currentSourceFiber = sourceFiber.alternate;
267
- if (currentSourceFiber === null) {
268
- // This is a new mount. Change the tag so it's not mistaken for a
269
- // completed class component. For example, we should not call
270
- // componentWillUnmount if it is deleted.
271
- sourceFiber.tag = IncompleteClassComponent;
272
- } else {
273
- // When we try rendering again, we should not reuse the current fiber,
274
- // since it's known to be in an inconsistent state. Use a force updte to
275
- // prevent a bail out.
276
- const update = createUpdate(Sync, null);
277
- update.tag = ForceUpdate;
278
- enqueueUpdate(sourceFiber, update);
279
- }
280
- }
281
-
282
- // The source fiber did not complete. Mark it with Sync priority to
283
- // indicate that it still has pending work.
284
- sourceFiber.expirationTime = Sync;
285
-
286
- // Exit without suspending.
287
- return;
288
- }
289
-
290
- // Confirmed that the boundary is in a concurrent mode tree. Continue
291
- // with the normal suspend path.
292
-
293
- attachPingListener(root, renderExpirationTime, thenable);
294
-
295
- workInProgress.effectTag |= ShouldCapture;
296
- workInProgress.expirationTime = renderExpirationTime;
297
-
298
- return;
299
- } else if (
300
- enableSuspenseServerRenderer &&
301
- workInProgress.tag === DehydratedSuspenseComponent
302
- ) {
303
- attachPingListener(root, renderExpirationTime, thenable);
304
-
305
- // Since we already have a current fiber, we can eagerly add a retry listener.
306
- let retryCache = workInProgress.memoizedState;
307
- if (retryCache === null) {
308
- retryCache = workInProgress.memoizedState = new PossiblyWeakSet();
309
- const current = workInProgress.alternate;
310
- invariant(
311
- current,
312
- 'A dehydrated suspense boundary must commit before trying to render. ' +
313
- 'This is probably a bug in React.',
314
- );
315
- current.memoizedState = retryCache;
316
- }
317
- // Memoize using the boundary fiber to prevent redundant listeners.
318
- if (!retryCache.has(thenable)) {
319
- retryCache.add(thenable);
320
- let retry = resolveRetryThenable.bind(null, workInProgress, thenable);
321
- if (enableSchedulerTracing) {
322
- retry = Schedule_tracing_wrap(retry);
323
- }
324
- thenable.then(retry, retry);
325
- }
326
- workInProgress.effectTag |= ShouldCapture;
327
- workInProgress.expirationTime = renderExpirationTime;
328
- return;
329
- }
330
- // This boundary already captured during this render. Continue to the next
331
- // boundary.
332
- workInProgress = workInProgress.return;
333
- } while (workInProgress !== null);
334
- // No boundary was found. Fallthrough to error mode.
335
- // TODO: Use invariant so the message is stripped in prod?
336
- value = new Error(
337
- (getComponentName(sourceFiber.type) || 'A React component') +
338
- ' suspended while rendering, but no fallback UI was specified.\n' +
339
- '\n' +
340
- 'Add a <Suspense fallback=...> component higher in the tree to ' +
341
- 'provide a loading indicator or placeholder to display.' +
342
- getStackByFiberInDevAndProd(sourceFiber),
343
- );
344
- }
345
-
346
- // We didn't find a boundary that could handle this type of exception. Start
347
- // over and traverse parent path again, this time treating the exception
348
- // as an error.
349
- renderDidError();
350
- value = createCapturedValue(value, sourceFiber);
351
- let workInProgress = returnFiber;
352
- do {
353
- switch (workInProgress.tag) {
354
- case HostRoot: {
355
- const errorInfo = value;
356
- workInProgress.effectTag |= ShouldCapture;
357
- workInProgress.expirationTime = renderExpirationTime;
358
- const update = createRootErrorUpdate(
359
- workInProgress,
360
- errorInfo,
361
- renderExpirationTime,
362
- );
363
- enqueueCapturedUpdate(workInProgress, update);
364
- return;
365
- }
366
- case ClassComponent:
367
- // Capture and retry
368
- const errorInfo = value;
369
- const ctor = workInProgress.type;
370
- const instance = workInProgress.stateNode;
371
- if (
372
- (workInProgress.effectTag & DidCapture) === NoEffect &&
373
- (typeof ctor.getDerivedStateFromError === 'function' ||
374
- (instance !== null &&
375
- typeof instance.componentDidCatch === 'function' &&
376
- !isAlreadyFailedLegacyErrorBoundary(instance)))
377
- ) {
378
- workInProgress.effectTag |= ShouldCapture;
379
- workInProgress.expirationTime = renderExpirationTime;
380
- // Schedule the error boundary to re-render using updated state
381
- const update = createClassErrorUpdate(
382
- workInProgress,
383
- errorInfo,
384
- renderExpirationTime,
385
- );
386
- enqueueCapturedUpdate(workInProgress, update);
387
- return;
388
- }
389
- break;
390
- default:
391
- break;
392
- }
393
- workInProgress = workInProgress.return;
394
- } while (workInProgress !== null);
395
-}
396
-
41
function unwindWork(
42
workInProgress: Fiber,
43
renderExpirationTime: ExpirationTime,
@@ -512,10 +156,4 @@ function unwindInterruptedWork(interruptedWork: Fiber) {
156
}
157
}
158
515
-export {
516
- throwException,
517
- unwindWork,
518
- unwindInterruptedWork,
519
- createRootErrorUpdate,
520
- createClassErrorUpdate,
521
-};
159
+export {unwindWork, unwindInterruptedWork};
packages/react-reconciler/src/ReactFiberWorkLoop.js
renamed
+2
-3
@@ -104,13 +104,12 @@ import {
104
} from './ReactFiberExpirationTime';
105
import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
106
import {completeWork} from './ReactFiberCompleteWork';
107
+import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork';
108
import {
109
throwException,
109
- unwindWork,
110
- unwindInterruptedWork,
110
createRootErrorUpdate,
111
createClassErrorUpdate,
113
-} from './ReactFiberUnwindWork';
112
+} from './ReactFiberThrow';
113
import {
114
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
115
commitLifeCycles as commitLayoutEffectOnFiber,
packages/react-reconciler/src/ReactUpdateQueue.js
+1
-1
@@ -102,7 +102,7 @@ import {
102
} from 'shared/ReactFeatureFlags';
103
104
import {StrictMode} from './ReactTypeOfMode';
105
-import {markRenderEventTimeAndConfig} from './ReactFiberScheduler';
105
+import {markRenderEventTimeAndConfig} from './ReactFiberWorkLoop';
106
107
import invariant from 'shared/invariant';
108
import warningWithoutStack from 'shared/warningWithoutStack';
packages/react-test-renderer/src/ReactTestRendererAct.js
+1
-1
@@ -6,7 +6,7 @@
6
*
7
* @flow
8
*/
9
-import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
9
+import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
10
11
import {
12
batchedUpdates,