Skip to content

Commit e6eb323

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 b617db3 commit e6eb323

File tree

4 files changed

+69
-115
lines changed

4 files changed

+69
-115
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 & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ if (__DEV__) {
155155
};
156156
}
157157

158-
function createUpdateQueue<State>(fiber: Fiber): UpdateQueue<State> {
158+
export function initializeUpdateQueue<State>(fiber: Fiber): void {
159159
const queue: UpdateQueue<State> = {
160160
baseState: fiber.memoizedState,
161161
baseQueue: null,
@@ -164,19 +164,25 @@ function createUpdateQueue<State>(fiber: Fiber): UpdateQueue<State> {
164164
},
165165
effects: null,
166166
};
167-
return queue;
167+
fiber.updateQueue = queue;
168168
}
169169

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

182188
export function createUpdate(
@@ -201,22 +207,13 @@ export function createUpdate(
201207
}
202208

203209
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
204-
let sharedQueue;
205-
let updateQueue = fiber.updateQueue;
210+
const updateQueue = fiber.updateQueue;
206211
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;
212+
// Only occurs if the fiber has been unmounted.
213+
return;
217214
}
218-
sharedQueue = updateQueue.shared;
219215

216+
const sharedQueue = updateQueue.shared;
220217
const pending = sharedQueue.pending;
221218
if (pending === null) {
222219
// This is the first update. Create a circular list.
@@ -229,7 +226,6 @@ export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
229226

230227
if (__DEV__) {
231228
if (
232-
fiber.tag === ClassComponent &&
233229
currentlyProcessingQueue === sharedQueue &&
234230
!didWarnUpdateInsideUpdate
235231
) {
@@ -249,48 +245,25 @@ export function enqueueCapturedUpdate<State>(
249245
workInProgress: Fiber,
250246
update: Update<State>,
251247
) {
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-
);
248+
const current = workInProgress.alternate;
249+
if (current !== null) {
250+
// Ensure the work-in-progress queue is a clone
251+
// cloneUpdateQueue(current, workInProgress);
266252
}
267253

254+
// Captured updates go only on the work-in-progress queue.
255+
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
268256
// Append the update to the end of the list.
269-
const last = workInProgressQueue.baseQueue;
257+
const last = queue.baseQueue;
270258
if (last === null) {
271-
workInProgressQueue.baseQueue = update.next = update;
259+
queue.baseQueue = update.next = update;
272260
update.next = update;
273261
} else {
274262
update.next = last.next;
275263
last.next = update;
276264
}
277265
}
278266

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-
294267
function getStateFromUpdate<State>(
295268
workInProgress: Fiber,
296269
queue: UpdateQueue<State>,
@@ -366,14 +339,14 @@ function getStateFromUpdate<State>(
366339

367340
export function processUpdateQueue<State>(
368341
workInProgress: Fiber,
369-
queue: UpdateQueue<State>,
370342
props: any,
371343
instance: any,
372344
renderExpirationTime: ExpirationTime,
373345
): void {
374-
hasForceUpdate = false;
346+
// This is always non-null on a ClassComponent or HostRoot
347+
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
375348

376-
queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);
349+
hasForceUpdate = false;
377350

378351
if (__DEV__) {
379352
currentlyProcessingQueue = queue.shared;

0 commit comments

Comments
 (0)