Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple update queue from Fiber type #12600

Merged
merged 10 commits into from Apr 23, 2018
Expand Up @@ -264,7 +264,6 @@ describe('createSubscription', () => {

it('should ignore values emitted by a new subscribable until the commit phase', () => {
const log = [];
let parentInstance;

function Child({value}) {
ReactNoop.yield('Child: ' + value);
Expand Down Expand Up @@ -301,8 +300,6 @@ describe('createSubscription', () => {
}

render() {
parentInstance = this;

return (
<Subscription source={this.state.observed}>
{(value = 'default') => {
Expand Down Expand Up @@ -331,8 +328,8 @@ describe('createSubscription', () => {
observableB.next('b-2');
observableB.next('b-3');

// Mimic a higher-priority interruption
parentInstance.setState({observed: observableA});
// Update again
ReactNoop.render(<Parent observed={observableA} />);

// Flush everything and ensure that the correct subscribable is used
// We expect the last emitted update to be rendered (because of the commit phase value check)
Expand All @@ -354,7 +351,6 @@ describe('createSubscription', () => {

it('should not drop values emitted between updates', () => {
const log = [];
let parentInstance;

function Child({value}) {
ReactNoop.yield('Child: ' + value);
Expand Down Expand Up @@ -391,8 +387,6 @@ describe('createSubscription', () => {
}

render() {
parentInstance = this;

return (
<Subscription source={this.state.observed}>
{(value = 'default') => {
Expand Down Expand Up @@ -420,8 +414,8 @@ describe('createSubscription', () => {
observableA.next('a-1');
observableA.next('a-2');

// Mimic a higher-priority interruption
parentInstance.setState({observed: observableA});
// Update again
ReactNoop.render(<Parent observed={observableA} />);

// Flush everything and ensure that the correct subscribable is used
// We expect the new subscribable to finish rendering,
Expand Down
16 changes: 4 additions & 12 deletions packages/react-noop-renderer/src/ReactNoop.js
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 Expand Up @@ -526,23 +526,15 @@ const ReactNoop = {

function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
const firstUpdate = updateQueue.first;
const firstUpdate = updateQueue.firstUpdate;
if (!firstUpdate) {
return;
}

log(
' '.repeat(depth + 1) + '~',
firstUpdate && firstUpdate.partialState,
firstUpdate.callback ? 'with callback' : '',
'[' + firstUpdate.expirationTime + ']',
);
let next;
while ((next = firstUpdate.next)) {
log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']');
while (firstUpdate.next) {
log(
' '.repeat(depth + 1) + '~',
next.partialState,
next.callback ? 'with callback' : '',
'[' + firstUpdate.expirationTime + ']',
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiber.js
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
73 changes: 28 additions & 45 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
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 @@ -52,13 +54,15 @@ import warning from 'fbjs/lib/warning';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import {cancelWorkTimer} from './ReactDebugFiberPerf';

import ReactFiberClassComponent from './ReactFiberClassComponent';
import ReactFiberClassComponent, {
applyDerivedStateFromProps,
} from './ReactFiberClassComponent';
import {
mountChildFibers,
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
import {processUpdateQueue} from './ReactFiberUpdateQueue';
import {processUpdateQueue} 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 +109,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 +263,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 +285,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 +299,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 +411,24 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
pushHostRootContext(workInProgress);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const state = processUpdateQueue(
current,
const prevChildren = prevState !== null ? prevState.children : null;
processUpdateQueue(
workInProgress,
updateQueue,
null,
nextProps,
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 nextState = workInProgress.memoizedState;
const nextChildren = nextState.children;

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 +453,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,21 +599,13 @@ 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(
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
value,
getDerivedStateFromProps,
props,
workInProgress.memoizedState,
);

if (partialState !== null && partialState !== undefined) {
workInProgress.memoizedState = Object.assign(
{},
workInProgress.memoizedState,
partialState,
);
}
}

// Push context providers early to prevent context stack mismatches.
Expand All @@ -635,7 +619,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress,
true,
hasContext,
false,
renderExpirationTime,
);
} else {
Expand Down