Skip to content

Commit 7768843

Browse files
committed
Initialize update queue object on mount
Instead of lazily initializing update queue objects on the first update, class and host root queues are created on mount. This simplifies the logic for appending new updates and matches what we do for hooks.
1 parent 66066ea commit 7768843

File tree

4 files changed

+69
-116
lines changed

4 files changed

+69
-116
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ import {
9090
reconcileChildFibers,
9191
cloneChildFibers,
9292
} from './ReactChildFiber';
93-
import {processUpdateQueue} from './ReactUpdateQueue';
93+
import {
94+
processUpdateQueue,
95+
cloneUpdateQueue,
96+
initializeUpdateQueue,
97+
} from './ReactUpdateQueue';
9498
import {
9599
NoWork,
96100
Never,
@@ -904,21 +908,16 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
904908
pushHostRootContext(workInProgress);
905909
const updateQueue = workInProgress.updateQueue;
906910
invariant(
907-
updateQueue !== null,
911+
current !== null && updateQueue !== null,
908912
'If the root does not have an updateQueue, we should have already ' +
909913
'bailed out. This error is likely caused by a bug in React. Please ' +
910914
'file an issue.',
911915
);
912916
const nextProps = workInProgress.pendingProps;
913917
const prevState = workInProgress.memoizedState;
914918
const prevChildren = prevState !== null ? prevState.element : null;
915-
processUpdateQueue(
916-
workInProgress,
917-
updateQueue,
918-
nextProps,
919-
null,
920-
renderExpirationTime,
921-
);
919+
cloneUpdateQueue(current, workInProgress);
920+
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
922921
const nextState = workInProgress.memoizedState;
923922
// Caution: React DevTools currently depends on this property
924923
// being called "element".
@@ -1338,6 +1337,8 @@ function mountIndeterminateComponent(
13381337
workInProgress.memoizedState =
13391338
value.state !== null && value.state !== undefined ? value.state : null;
13401339

1340+
initializeUpdateQueue(workInProgress);
1341+
13411342
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
13421343
if (typeof getDerivedStateFromProps === 'function') {
13431344
applyDerivedStateFromProps(

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {Fiber} from './ReactFiber';
1111
import type {ExpirationTime} from './ReactFiberExpirationTime';
12+
import type {UpdateQueue} from './ReactUpdateQueue';
1213

1314
import React from 'react';
1415
import {Update, Snapshot} from 'shared/ReactSideEffectTags';
@@ -38,6 +39,8 @@ import {
3839
createUpdate,
3940
ReplaceState,
4041
ForceUpdate,
42+
initializeUpdateQueue,
43+
cloneUpdateQueue,
4144
} from './ReactUpdateQueue';
4245
import {NoWork} from './ReactFiberExpirationTime';
4346
import {
@@ -171,8 +174,9 @@ export function applyDerivedStateFromProps(
171174

172175
// Once the update queue is empty, persist the derived state onto the
173176
// base state.
174-
const updateQueue = workInProgress.updateQueue;
175-
if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
177+
if (workInProgress.expirationTime === NoWork) {
178+
// Queue is always non-null for classes
179+
const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
176180
updateQueue.baseState = memoizedState;
177181
}
178182
}
@@ -789,6 +793,8 @@ function mountClassInstance(
789793
instance.state = workInProgress.memoizedState;
790794
instance.refs = emptyRefsObject;
791795

796+
initializeUpdateQueue(workInProgress);
797+
792798
const contextType = ctor.contextType;
793799
if (typeof contextType === 'object' && contextType !== null) {
794800
instance.context = readContext(contextType);
@@ -829,17 +835,8 @@ function mountClassInstance(
829835
}
830836
}
831837

832-
let updateQueue = workInProgress.updateQueue;
833-
if (updateQueue !== null) {
834-
processUpdateQueue(
835-
workInProgress,
836-
updateQueue,
837-
newProps,
838-
instance,
839-
renderExpirationTime,
840-
);
841-
instance.state = workInProgress.memoizedState;
842-
}
838+
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
839+
instance.state = workInProgress.memoizedState;
843840

844841
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
845842
if (typeof getDerivedStateFromProps === 'function') {
@@ -863,17 +860,13 @@ function mountClassInstance(
863860
callComponentWillMount(workInProgress, instance);
864861
// If we had additional state updates during this life-cycle, let's
865862
// process them now.
866-
updateQueue = workInProgress.updateQueue;
867-
if (updateQueue !== null) {
868-
processUpdateQueue(
869-
workInProgress,
870-
updateQueue,
871-
newProps,
872-
instance,
873-
renderExpirationTime,
874-
);
875-
instance.state = workInProgress.memoizedState;
876-
}
863+
processUpdateQueue(
864+
workInProgress,
865+
newProps,
866+
instance,
867+
renderExpirationTime,
868+
);
869+
instance.state = workInProgress.memoizedState;
877870
}
878871

879872
if (typeof instance.componentDidMount === 'function') {
@@ -936,17 +929,8 @@ function resumeMountClassInstance(
936929

937930
const oldState = workInProgress.memoizedState;
938931
let newState = (instance.state = oldState);
939-
let updateQueue = workInProgress.updateQueue;
940-
if (updateQueue !== null) {
941-
processUpdateQueue(
942-
workInProgress,
943-
updateQueue,
944-
newProps,
945-
instance,
946-
renderExpirationTime,
947-
);
948-
newState = workInProgress.memoizedState;
949-
}
932+
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
933+
newState = workInProgress.memoizedState;
950934
if (
951935
oldProps === newProps &&
952936
oldState === newState &&
@@ -1035,6 +1019,8 @@ function updateClassInstance(
10351019
): boolean {
10361020
const instance = workInProgress.stateNode;
10371021

1022+
cloneUpdateQueue(current, workInProgress);
1023+
10381024
const oldProps = workInProgress.memoizedProps;
10391025
instance.props =
10401026
workInProgress.type === workInProgress.elementType
@@ -1081,17 +1067,8 @@ function updateClassInstance(
10811067

10821068
const oldState = workInProgress.memoizedState;
10831069
let newState = (instance.state = oldState);
1084-
let updateQueue = workInProgress.updateQueue;
1085-
if (updateQueue !== null) {
1086-
processUpdateQueue(
1087-
workInProgress,
1088-
updateQueue,
1089-
newProps,
1090-
instance,
1091-
renderExpirationTime,
1092-
);
1093-
newState = workInProgress.memoizedState;
1094-
}
1070+
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
1071+
newState = workInProgress.memoizedState;
10951072

10961073
if (
10971074
oldProps === newProps &&

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
} from 'shared/ReactFeatureFlags';
2626
import {unstable_getThreadID} from 'scheduler/tracing';
2727
import {NoPriority} from './SchedulerWithReactIntegration';
28+
import {initializeUpdateQueue} from './ReactUpdateQueue';
2829

2930
export type PendingInteractionMap = Map<ExpirationTime, Set<Interaction>>;
3031

@@ -149,6 +150,8 @@ export function createFiberRoot(
149150
root.current = uninitializedFiber;
150151
uninitializedFiber.stateNode = root;
151152

153+
initializeUpdateQueue(uninitializedFiber);
154+
152155
return root;
153156
}
154157

packages/react-reconciler/src/ReactUpdateQueue.js

Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ import {
9595
exitDisallowedContextReadInDEV,
9696
} from './ReactFiberNewContext';
9797
import {Callback, ShouldCapture, DidCapture} from 'shared/ReactSideEffectTags';
98-
import {ClassComponent} from 'shared/ReactWorkTags';
9998

10099
import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
101100

@@ -155,7 +154,7 @@ if (__DEV__) {
155154
};
156155
}
157156

158-
function createUpdateQueue<State>(fiber: Fiber): UpdateQueue<State> {
157+
export function initializeUpdateQueue<State>(fiber: Fiber): void {
159158
const queue: UpdateQueue<State> = {
160159
baseState: fiber.memoizedState,
161160
baseQueue: null,
@@ -164,19 +163,25 @@ function createUpdateQueue<State>(fiber: Fiber): UpdateQueue<State> {
164163
},
165164
effects: null,
166165
};
167-
return queue;
166+
fiber.updateQueue = queue;
168167
}
169168

170-
function cloneUpdateQueue<State>(
171-
currentQueue: UpdateQueue<State>,
172-
): UpdateQueue<State> {
173-
const queue: UpdateQueue<State> = {
174-
baseState: currentQueue.baseState,
175-
baseQueue: currentQueue.baseQueue,
176-
shared: currentQueue.shared,
177-
effects: null,
178-
};
179-
return queue;
169+
export function cloneUpdateQueue<State>(
170+
current: Fiber,
171+
workInProgress: Fiber,
172+
): void {
173+
// Clone the update queue from current. Unless it's already a clone.
174+
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
175+
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
176+
if (queue === currentQueue) {
177+
const clone: UpdateQueue<State> = {
178+
baseState: currentQueue.baseState,
179+
baseQueue: currentQueue.baseQueue,
180+
shared: currentQueue.shared,
181+
effects: currentQueue.effects,
182+
};
183+
workInProgress.updateQueue = clone;
184+
}
180185
}
181186

182187
export function createUpdate(
@@ -201,22 +206,13 @@ export function createUpdate(
201206
}
202207

203208
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
204-
let sharedQueue;
205-
let updateQueue = fiber.updateQueue;
209+
const updateQueue = fiber.updateQueue;
206210
if (updateQueue === null) {
207-
const alternate = fiber.alternate;
208-
if (alternate === null) {
209-
updateQueue = createUpdateQueue(fiber);
210-
} else {
211-
updateQueue = alternate.updateQueue;
212-
if (updateQueue === null) {
213-
updateQueue = alternate.updateQueue = createUpdateQueue(alternate);
214-
}
215-
}
216-
fiber.updateQueue = updateQueue;
211+
// Only occurs if the fiber has been unmounted.
212+
return;
217213
}
218-
sharedQueue = updateQueue.shared;
219214

215+
const sharedQueue = updateQueue.shared;
220216
const pending = sharedQueue.pending;
221217
if (pending === null) {
222218
// This is the first update. Create a circular list.
@@ -229,7 +225,6 @@ export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
229225

230226
if (__DEV__) {
231227
if (
232-
fiber.tag === ClassComponent &&
233228
currentlyProcessingQueue === sharedQueue &&
234229
!didWarnUpdateInsideUpdate
235230
) {
@@ -249,48 +244,25 @@ export function enqueueCapturedUpdate<State>(
249244
workInProgress: Fiber,
250245
update: Update<State>,
251246
) {
252-
// Captured updates go only on the work-in-progress queue.
253-
let workInProgressQueue = workInProgress.updateQueue;
254-
if (workInProgressQueue === null) {
255-
workInProgressQueue = workInProgress.updateQueue = createUpdateQueue(
256-
workInProgress,
257-
);
258-
} else {
259-
// TODO: I put this here rather than createWorkInProgress so that we don't
260-
// clone the queue unnecessarily. There's probably a better way to
261-
// structure this.
262-
workInProgressQueue = ensureWorkInProgressQueueIsAClone(
263-
workInProgress,
264-
workInProgressQueue,
265-
);
247+
const current = workInProgress.alternate;
248+
if (current !== null) {
249+
// Ensure the work-in-progress queue is a clone
250+
cloneUpdateQueue(current, workInProgress);
266251
}
267252

253+
// Captured updates go only on the work-in-progress queue.
254+
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
268255
// Append the update to the end of the list.
269-
const last = workInProgressQueue.baseQueue;
256+
const last = queue.baseQueue;
270257
if (last === null) {
271-
workInProgressQueue.baseQueue = update.next = update;
258+
queue.baseQueue = update.next = update;
272259
update.next = update;
273260
} else {
274261
update.next = last.next;
275262
last.next = update;
276263
}
277264
}
278265

279-
function ensureWorkInProgressQueueIsAClone<State>(
280-
workInProgress: Fiber,
281-
queue: UpdateQueue<State>,
282-
): UpdateQueue<State> {
283-
const current = workInProgress.alternate;
284-
if (current !== null) {
285-
// If the work-in-progress queue is equal to the current queue,
286-
// we need to clone it first.
287-
if (queue === current.updateQueue) {
288-
queue = workInProgress.updateQueue = cloneUpdateQueue(queue);
289-
}
290-
}
291-
return queue;
292-
}
293-
294266
function getStateFromUpdate<State>(
295267
workInProgress: Fiber,
296268
queue: UpdateQueue<State>,
@@ -366,14 +338,14 @@ function getStateFromUpdate<State>(
366338

367339
export function processUpdateQueue<State>(
368340
workInProgress: Fiber,
369-
queue: UpdateQueue<State>,
370341
props: any,
371342
instance: any,
372343
renderExpirationTime: ExpirationTime,
373344
): void {
374-
hasForceUpdate = false;
345+
// This is always non-null on a ClassComponent or HostRoot
346+
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
375347

376-
queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);
348+
hasForceUpdate = false;
377349

378350
if (__DEV__) {
379351
currentlyProcessingQueue = queue.shared;

0 commit comments

Comments
 (0)