Skip to content

Commit

Permalink
Initialize update queue object on mount
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Dec 9, 2019
1 parent b617db3 commit e6eb323
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 115 deletions.
19 changes: 10 additions & 9 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ import {
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
import {processUpdateQueue} from './ReactUpdateQueue';
import {
processUpdateQueue,
cloneUpdateQueue,
initializeUpdateQueue,
} from './ReactUpdateQueue';
import {
NoWork,
Never,
Expand Down Expand Up @@ -904,21 +908,16 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
const updateQueue = workInProgress.updateQueue;
invariant(
updateQueue !== null,
current !== null && updateQueue !== null,
'If the root does not have an updateQueue, we should have already ' +
'bailed out. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
processUpdateQueue(
workInProgress,
updateQueue,
nextProps,
null,
renderExpirationTime,
);
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
Expand Down Expand Up @@ -1338,6 +1337,8 @@ function mountIndeterminateComponent(
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;

initializeUpdateQueue(workInProgress);

const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
Expand Down
69 changes: 23 additions & 46 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {UpdateQueue} from './ReactUpdateQueue';

import React from 'react';
import {Update, Snapshot} from 'shared/ReactSideEffectTags';
Expand Down Expand Up @@ -38,6 +39,8 @@ import {
createUpdate,
ReplaceState,
ForceUpdate,
initializeUpdateQueue,
cloneUpdateQueue,
} from './ReactUpdateQueue';
import {NoWork} from './ReactFiberExpirationTime';
import {
Expand Down Expand Up @@ -171,8 +174,9 @@ export function applyDerivedStateFromProps(

// Once the update queue is empty, persist the derived state onto the
// base state.
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
if (workInProgress.expirationTime === NoWork) {
// Queue is always non-null for classes
const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
updateQueue.baseState = memoizedState;
}
}
Expand Down Expand Up @@ -789,6 +793,8 @@ function mountClassInstance(
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;

initializeUpdateQueue(workInProgress);

const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
instance.context = readContext(contextType);
Expand Down Expand Up @@ -829,17 +835,8 @@ function mountClassInstance(
}
}

let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
instance.state = workInProgress.memoizedState;

const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
Expand All @@ -863,17 +860,13 @@ function mountClassInstance(
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
processUpdateQueue(
workInProgress,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}

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

const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
newState = workInProgress.memoizedState;
if (
oldProps === newProps &&
oldState === newState &&
Expand Down Expand Up @@ -1035,6 +1019,8 @@ function updateClassInstance(
): boolean {
const instance = workInProgress.stateNode;

cloneUpdateQueue(current, workInProgress);

const oldProps = workInProgress.memoizedProps;
instance.props =
workInProgress.type === workInProgress.elementType
Expand Down Expand Up @@ -1081,17 +1067,8 @@ function updateClassInstance(

const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
newState = workInProgress.memoizedState;

if (
oldProps === newProps &&
Expand Down
3 changes: 3 additions & 0 deletions packages/react-reconciler/src/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {NoPriority} from './SchedulerWithReactIntegration';
import {initializeUpdateQueue} from './ReactUpdateQueue';

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

Expand Down Expand Up @@ -149,6 +150,8 @@ export function createFiberRoot(
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

initializeUpdateQueue(uninitializedFiber);

return root;
}

Expand Down
93 changes: 33 additions & 60 deletions packages/react-reconciler/src/ReactUpdateQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ if (__DEV__) {
};
}

function createUpdateQueue<State>(fiber: Fiber): UpdateQueue<State> {
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
baseQueue: null,
Expand All @@ -164,19 +164,25 @@ function createUpdateQueue<State>(fiber: Fiber): UpdateQueue<State> {
},
effects: null,
};
return queue;
fiber.updateQueue = queue;
}

function cloneUpdateQueue<State>(
currentQueue: UpdateQueue<State>,
): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
baseState: currentQueue.baseState,
baseQueue: currentQueue.baseQueue,
shared: currentQueue.shared,
effects: null,
};
return queue;
export function cloneUpdateQueue<State>(
current: Fiber,
workInProgress: Fiber,
): void {
// Clone the update queue from current. Unless it's already a clone.
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
if (queue === currentQueue) {
const clone: UpdateQueue<State> = {
baseState: currentQueue.baseState,
baseQueue: currentQueue.baseQueue,
shared: currentQueue.shared,
effects: currentQueue.effects,
};
workInProgress.updateQueue = clone;
}
}

export function createUpdate(
Expand All @@ -201,22 +207,13 @@ export function createUpdate(
}

export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
let sharedQueue;
let updateQueue = fiber.updateQueue;
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
const alternate = fiber.alternate;
if (alternate === null) {
updateQueue = createUpdateQueue(fiber);
} else {
updateQueue = alternate.updateQueue;
if (updateQueue === null) {
updateQueue = alternate.updateQueue = createUpdateQueue(alternate);
}
}
fiber.updateQueue = updateQueue;
// Only occurs if the fiber has been unmounted.
return;
}
sharedQueue = updateQueue.shared;

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

if (__DEV__) {
if (
fiber.tag === ClassComponent &&
currentlyProcessingQueue === sharedQueue &&
!didWarnUpdateInsideUpdate
) {
Expand All @@ -249,48 +245,25 @@ export function enqueueCapturedUpdate<State>(
workInProgress: Fiber,
update: Update<State>,
) {
// Captured updates go only on the work-in-progress queue.
let workInProgressQueue = workInProgress.updateQueue;
if (workInProgressQueue === null) {
workInProgressQueue = workInProgress.updateQueue = createUpdateQueue(
workInProgress,
);
} else {
// TODO: I put this here rather than createWorkInProgress so that we don't
// clone the queue unnecessarily. There's probably a better way to
// structure this.
workInProgressQueue = ensureWorkInProgressQueueIsAClone(
workInProgress,
workInProgressQueue,
);
const current = workInProgress.alternate;
if (current !== null) {
// Ensure the work-in-progress queue is a clone
// cloneUpdateQueue(current, workInProgress);
}

// Captured updates go only on the work-in-progress queue.
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
// Append the update to the end of the list.
const last = workInProgressQueue.baseQueue;
const last = queue.baseQueue;
if (last === null) {
workInProgressQueue.baseQueue = update.next = update;
queue.baseQueue = update.next = update;
update.next = update;
} else {
update.next = last.next;
last.next = update;
}
}

function ensureWorkInProgressQueueIsAClone<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
): UpdateQueue<State> {
const current = workInProgress.alternate;
if (current !== null) {
// If the work-in-progress queue is equal to the current queue,
// we need to clone it first.
if (queue === current.updateQueue) {
queue = workInProgress.updateQueue = cloneUpdateQueue(queue);
}
}
return queue;
}

function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
Expand Down Expand Up @@ -366,14 +339,14 @@ function getStateFromUpdate<State>(

export function processUpdateQueue<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
hasForceUpdate = false;
// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);

queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);
hasForceUpdate = false;

if (__DEV__) {
currentlyProcessingQueue = queue.shared;
Expand Down

0 comments on commit e6eb323

Please sign in to comment.