Skip to content

Commit

Permalink
[WIP] Decouple update queue from Fiber type
Browse files Browse the repository at this point in the history
The update queue is in need of a refactor. Recent bugfixes (#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`.

TODO:

- [x] Class components
- [x] Host roots
- [ ] Fix Flow
  • Loading branch information
acdlite committed Apr 11, 2018
1 parent 76b4ba0 commit e325697
Show file tree
Hide file tree
Showing 15 changed files with 1,322 additions and 1,001 deletions.
2 changes: 1 addition & 1 deletion packages/react-noop-renderer/src/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberUpdateQueue';
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
import type {ReactNodeList} from 'shared/ReactTypes';
import ReactFiberReconciler from 'react-reconciler';
import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {TypeOfWork} from 'shared/ReactTypeOfWork';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {UpdateQueue} from './ReactFiberUpdateQueue';
import type {UpdateQueue} from './ReactUpdateQueue';

import invariant from 'fbjs/lib/invariant';
import {NoEffect} from 'shared/ReactTypeOfSideEffect';
Expand Down
86 changes: 34 additions & 52 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ import {
ContextConsumer,
} from 'shared/ReactTypeOfWork';
import {
NoEffect,
PerformedWork,
Placement,
ContentReset,
Ref,
DidCapture,
} from 'shared/ReactTypeOfSideEffect';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import {
Expand All @@ -58,7 +60,12 @@ import {
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
import {processUpdateQueue} from './ReactFiberUpdateQueue';
import {
createDeriveStateFromPropsUpdate,
enqueueRenderPhaseUpdate,
processClassUpdateQueue,
processRootUpdateQueue,
} from './ReactUpdateQueue';
import {NoWork, Never} from './ReactFiberExpirationTime';
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
Expand Down Expand Up @@ -105,7 +112,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(

const {
adoptClassInstance,
callGetDerivedStateFromProps,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
Expand Down Expand Up @@ -260,7 +266,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
if (current === null) {
if (workInProgress.stateNode === null) {
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, workInProgress.pendingProps);
constructClassInstance(
workInProgress,
workInProgress.pendingProps,
renderExpirationTime,
);
mountClassInstance(workInProgress, renderExpirationTime);

shouldUpdate = true;
Expand All @@ -278,22 +288,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
renderExpirationTime,
);
}

// We processed the update queue inside updateClassInstance. It may have
// included some errors that were dispatched during the commit phase.
// TODO: Refactor class components so this is less awkward.
let didCaptureError = false;
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null && updateQueue.capturedValues !== null) {
shouldUpdate = true;
didCaptureError = true;
}
return finishClassComponent(
current,
workInProgress,
shouldUpdate,
hasContext,
didCaptureError,
renderExpirationTime,
);
}
Expand All @@ -303,12 +302,14 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress: Fiber,
shouldUpdate: boolean,
hasContext: boolean,
didCaptureError: boolean,
renderExpirationTime: ExpirationTime,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);

const didCaptureError =
(workInProgress.effectTag & DidCapture) !== NoEffect;

if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
Expand Down Expand Up @@ -413,29 +414,15 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
pushHostRootContext(workInProgress);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
const prevState = workInProgress.memoizedState;
const state = processUpdateQueue(
current,
workInProgress,
updateQueue,
null,
null,
renderExpirationTime,
);
memoizeState(workInProgress, state);
updateQueue = workInProgress.updateQueue;

let element;
if (updateQueue !== null && updateQueue.capturedValues !== null) {
// There's an uncaught error. Unmount the whole root.
element = null;
} else if (prevState === state) {
const prevChildren = workInProgress.memoizedState;
processRootUpdateQueue(workInProgress, updateQueue, renderExpirationTime);
const nextChildren = workInProgress.memoizedState;

if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current, workInProgress);
} else {
element = state.element;
}
const root: FiberRoot = workInProgress.stateNode;
if (
Expand All @@ -460,16 +447,15 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress.child = mountChildFibers(
workInProgress,
null,
element,
nextChildren,
renderExpirationTime,
);
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
resetHydrationState();
reconcileChildren(current, workInProgress, element);
reconcileChildren(current, workInProgress, nextChildren);
}
memoizeState(workInProgress, state);
return workInProgress.child;
}
resetHydrationState();
Expand Down Expand Up @@ -607,19 +593,16 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;

if (typeof Component.getDerivedStateFromProps === 'function') {
const partialState = callGetDerivedStateFromProps(
workInProgress,
value,
props,
workInProgress.memoizedState,
);

if (partialState !== null && partialState !== undefined) {
workInProgress.memoizedState = Object.assign(
{},
workInProgress.memoizedState,
partialState,
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
const update = createDeriveStateFromPropsUpdate(renderExpirationTime);
enqueueRenderPhaseUpdate(workInProgress, update, renderExpirationTime);
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processClassUpdateQueue(
workInProgress,
updateQueue,
renderExpirationTime,
);
}
}
Expand All @@ -635,7 +618,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress,
true,
hasContext,
false,
renderExpirationTime,
);
} else {
Expand Down Expand Up @@ -1098,7 +1080,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
function memoizeState(workInProgress: Fiber, nextState: any) {
workInProgress.memoizedState = nextState;
// Don't reset the updateQueue, in case there are pending updates. Resetting
// is handled by processUpdateQueue.
// is handled by processClassUpdateQueue.
}

function beginWork(
Expand Down
Loading

0 comments on commit e325697

Please sign in to comment.