Skip to content

Commit 2bc15bf

Browse files
committed
Decouple update queue from Fiber type
The update queue is in need of a refactor. Recent bugfixes (facebook#12528) have exposed some flaws in how it's modeled. Upcoming features like Suspense and [redacted] also rely on the update queue in ways that weren't anticipated in the original design. Major changes: - Instead of boolean flags for `isReplace` and `isForceUpdate`, updates have a `tag` field (like Fiber). This lowers the cost for adding new types of updates. - Render phase updates are special cased. Updates scheduled during the render phase are dropped if the work-in-progress does not commit. This is used for `getDerivedStateFrom{Props,Catch}`. - `callbackList` has been replaced with a generic effect list. Aside from callbacks, this is also used for `componentDidCatch`.
1 parent 999b656 commit 2bc15bf

15 files changed

+1315
-1001
lines changed

packages/react-noop-renderer/src/ReactNoop.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import type {Fiber} from 'react-reconciler/src/ReactFiber';
18-
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberUpdateQueue';
18+
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
1919
import type {ReactNodeList} from 'shared/ReactTypes';
2020
import ReactFiberReconciler from 'react-reconciler';
2121
import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';

packages/react-reconciler/src/ReactFiber.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {TypeOfWork} from 'shared/ReactTypeOfWork';
1212
import type {TypeOfMode} from './ReactTypeOfMode';
1313
import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
1414
import type {ExpirationTime} from './ReactFiberExpirationTime';
15-
import type {UpdateQueue} from './ReactFiberUpdateQueue';
15+
import type {UpdateQueue} from './ReactUpdateQueue';
1616

1717
import invariant from 'fbjs/lib/invariant';
1818
import {NoEffect} from 'shared/ReactTypeOfSideEffect';

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ import {
3535
ContextConsumer,
3636
} from 'shared/ReactTypeOfWork';
3737
import {
38+
NoEffect,
3839
PerformedWork,
3940
Placement,
4041
ContentReset,
4142
Ref,
43+
DidCapture,
4244
} from 'shared/ReactTypeOfSideEffect';
4345
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
4446
import {
@@ -58,7 +60,12 @@ import {
5860
reconcileChildFibers,
5961
cloneChildFibers,
6062
} from './ReactChildFiber';
61-
import {processUpdateQueue} from './ReactFiberUpdateQueue';
63+
import {
64+
createDeriveStateFromPropsUpdate,
65+
enqueueRenderPhaseUpdate,
66+
processClassUpdateQueue,
67+
processRootUpdateQueue,
68+
} from './ReactUpdateQueue';
6269
import {NoWork, Never} from './ReactFiberExpirationTime';
6370
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
6471
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
@@ -105,7 +112,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
105112

106113
const {
107114
adoptClassInstance,
108-
callGetDerivedStateFromProps,
109115
constructClassInstance,
110116
mountClassInstance,
111117
resumeMountClassInstance,
@@ -260,7 +266,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
260266
if (current === null) {
261267
if (workInProgress.stateNode === null) {
262268
// In the initial pass we might need to construct the instance.
263-
constructClassInstance(workInProgress, workInProgress.pendingProps);
269+
constructClassInstance(
270+
workInProgress,
271+
workInProgress.pendingProps,
272+
renderExpirationTime,
273+
);
264274
mountClassInstance(workInProgress, renderExpirationTime);
265275

266276
shouldUpdate = true;
@@ -278,22 +288,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
278288
renderExpirationTime,
279289
);
280290
}
281-
282-
// We processed the update queue inside updateClassInstance. It may have
283-
// included some errors that were dispatched during the commit phase.
284-
// TODO: Refactor class components so this is less awkward.
285-
let didCaptureError = false;
286-
const updateQueue = workInProgress.updateQueue;
287-
if (updateQueue !== null && updateQueue.capturedValues !== null) {
288-
shouldUpdate = true;
289-
didCaptureError = true;
290-
}
291291
return finishClassComponent(
292292
current,
293293
workInProgress,
294294
shouldUpdate,
295295
hasContext,
296-
didCaptureError,
297296
renderExpirationTime,
298297
);
299298
}
@@ -303,12 +302,14 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
303302
workInProgress: Fiber,
304303
shouldUpdate: boolean,
305304
hasContext: boolean,
306-
didCaptureError: boolean,
307305
renderExpirationTime: ExpirationTime,
308306
) {
309307
// Refs should update even if shouldComponentUpdate returns false
310308
markRef(current, workInProgress);
311309

310+
const didCaptureError =
311+
(workInProgress.effectTag & DidCapture) !== NoEffect;
312+
312313
if (!shouldUpdate && !didCaptureError) {
313314
// Context providers should defer to sCU for rendering
314315
if (hasContext) {
@@ -413,29 +414,15 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
413414
pushHostRootContext(workInProgress);
414415
let updateQueue = workInProgress.updateQueue;
415416
if (updateQueue !== null) {
416-
const prevState = workInProgress.memoizedState;
417-
const state = processUpdateQueue(
418-
current,
419-
workInProgress,
420-
updateQueue,
421-
null,
422-
null,
423-
renderExpirationTime,
424-
);
425-
memoizeState(workInProgress, state);
426-
updateQueue = workInProgress.updateQueue;
427-
428-
let element;
429-
if (updateQueue !== null && updateQueue.capturedValues !== null) {
430-
// There's an uncaught error. Unmount the whole root.
431-
element = null;
432-
} else if (prevState === state) {
417+
const prevChildren = workInProgress.memoizedState;
418+
processRootUpdateQueue(workInProgress, updateQueue, renderExpirationTime);
419+
const nextChildren = workInProgress.memoizedState;
420+
421+
if (nextChildren === prevChildren) {
433422
// If the state is the same as before, that's a bailout because we had
434423
// no work that expires at this time.
435424
resetHydrationState();
436425
return bailoutOnAlreadyFinishedWork(current, workInProgress);
437-
} else {
438-
element = state.element;
439426
}
440427
const root: FiberRoot = workInProgress.stateNode;
441428
if (
@@ -460,16 +447,15 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
460447
workInProgress.child = mountChildFibers(
461448
workInProgress,
462449
null,
463-
element,
450+
nextChildren,
464451
renderExpirationTime,
465452
);
466453
} else {
467454
// Otherwise reset hydration state in case we aborted and resumed another
468455
// root.
469456
resetHydrationState();
470-
reconcileChildren(current, workInProgress, element);
457+
reconcileChildren(current, workInProgress, nextChildren);
471458
}
472-
memoizeState(workInProgress, state);
473459
return workInProgress.child;
474460
}
475461
resetHydrationState();
@@ -607,19 +593,16 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
607593
workInProgress.memoizedState =
608594
value.state !== null && value.state !== undefined ? value.state : null;
609595

610-
if (typeof Component.getDerivedStateFromProps === 'function') {
611-
const partialState = callGetDerivedStateFromProps(
612-
workInProgress,
613-
value,
614-
props,
615-
workInProgress.memoizedState,
616-
);
617-
618-
if (partialState !== null && partialState !== undefined) {
619-
workInProgress.memoizedState = Object.assign(
620-
{},
621-
workInProgress.memoizedState,
622-
partialState,
596+
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
597+
if (typeof getDerivedStateFromProps === 'function') {
598+
const update = createDeriveStateFromPropsUpdate(renderExpirationTime);
599+
enqueueRenderPhaseUpdate(workInProgress, update, renderExpirationTime);
600+
const updateQueue = workInProgress.updateQueue;
601+
if (updateQueue !== null) {
602+
processClassUpdateQueue(
603+
workInProgress,
604+
updateQueue,
605+
renderExpirationTime,
623606
);
624607
}
625608
}
@@ -635,7 +618,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
635618
workInProgress,
636619
true,
637620
hasContext,
638-
false,
639621
renderExpirationTime,
640622
);
641623
} else {
@@ -1098,7 +1080,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
10981080
function memoizeState(workInProgress: Fiber, nextState: any) {
10991081
workInProgress.memoizedState = nextState;
11001082
// Don't reset the updateQueue, in case there are pending updates. Resetting
1101-
// is handled by processUpdateQueue.
1083+
// is handled by processClassUpdateQueue.
11021084
}
11031085

11041086
function beginWork(

0 commit comments

Comments
 (0)