Files
react/packages/react-reconciler/src/ReactFiberClassComponent.js
Brian Vaughn d3b183c323 Debug render-phase side effects in "strict" mode (#12094)
A new feature flag has been added, debugRenderPhaseSideEffectsForStrictMode. When enabled, StrictMode subtrees will also double-invoke lifecycles in the same way as debugRenderPhaseSideEffects.

By default, this flag is enabled for __DEV__ only. Internally we can toggle it with a GK.

This breaks several of our incremental tests which make use of the noop-renderer. Updating the tests to account for the double-rendering in development mode makes them significantly more complicated. The most straight forward fix for this will be to convert them to be run as internal tests only. I believe this is reasonable since we are the only people making use of the noop renderer.
2018-01-25 14:30:53 -08:00

912 lines
29 KiB
JavaScript

/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import {Update} from 'shared/ReactTypeOfSideEffect';
import {
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
enableAsyncSubtreeAPI,
warnAboutDeprecatedLifecycles,
} from 'shared/ReactFeatureFlags';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import {isMounted} from 'react-reconciler/reflection';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import emptyObject from 'fbjs/lib/emptyObject';
import getComponentName from 'shared/getComponentName';
import shallowEqual from 'fbjs/lib/shallowEqual';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
import {AsyncUpdates, StrictMode} from './ReactTypeOfInternalContext';
import {
cacheContext,
getMaskedContext,
getUnmaskedContext,
isContextConsumer,
} from './ReactFiberContext';
import {
insertUpdateIntoFiber,
processUpdateQueue,
} from './ReactFiberUpdateQueue';
import {hasContextChanged} from './ReactFiberContext';
const fakeInternalInstance = {};
const isArray = Array.isArray;
let didWarnAboutStateAssignmentForComponent;
let didWarnAboutUndefinedDerivedState;
let didWarnAboutUninitializedState;
let didWarnAboutWillReceivePropsAndDerivedState;
let warnOnInvalidCallback;
if (__DEV__) {
didWarnAboutStateAssignmentForComponent = {};
didWarnAboutUndefinedDerivedState = {};
didWarnAboutUninitializedState = {};
didWarnAboutWillReceivePropsAndDerivedState = {};
const didWarnOnInvalidCallback = {};
warnOnInvalidCallback = function(callback: mixed, callerName: string) {
if (callback === null || typeof callback === 'function') {
return;
}
const key = `${callerName}_${(callback: any)}`;
if (!didWarnOnInvalidCallback[key]) {
warning(
false,
'%s(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callerName,
callback,
);
didWarnOnInvalidCallback[key] = true;
}
};
// This is so gross but it's at least non-critical and can be removed if
// it causes problems. This is meant to give a nicer error message for
// ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component,
// ...)) which otherwise throws a "_processChildContext is not a function"
// exception.
Object.defineProperty(fakeInternalInstance, '_processChildContext', {
enumerable: false,
value: function() {
invariant(
false,
'_processChildContext is not available in React 16+. This likely ' +
'means you have multiple copies of React and are attempting to nest ' +
'a React 15 tree inside a React 16 tree using ' +
"unstable_renderSubtreeIntoContainer, which isn't supported. Try " +
'to make sure you have only one copy of React (and ideally, switch ' +
'to ReactDOM.createPortal).',
);
},
});
Object.freeze(fakeInternalInstance);
}
export default function(
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
memoizeProps: (workInProgress: Fiber, props: any) => void,
memoizeState: (workInProgress: Fiber, state: any) => void,
) {
// Class component state updater
const updater = {
isMounted,
enqueueSetState(instance, partialState, callback) {
const fiber = ReactInstanceMap.get(instance);
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState,
callback,
isReplace: false,
isForced: false,
nextCallback: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueReplaceState(instance, state, callback) {
const fiber = ReactInstanceMap.get(instance);
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'replaceState');
}
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: state,
callback,
isReplace: true,
isForced: false,
nextCallback: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueForceUpdate(instance, callback) {
const fiber = ReactInstanceMap.get(instance);
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'forceUpdate');
}
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: null,
callback,
isReplace: false,
isForced: true,
nextCallback: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
};
function checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
) {
if (
oldProps === null ||
(workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate)
) {
// If the workInProgress already has an Update effect, return true
return true;
}
const instance = workInProgress.stateNode;
const type = workInProgress.type;
if (typeof instance.shouldComponentUpdate === 'function') {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
newContext,
);
stopPhaseTimer();
if (__DEV__) {
warning(
shouldUpdate !== undefined,
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
'boolean value. Make sure to return true or false.',
getComponentName(workInProgress) || 'Unknown',
);
}
return shouldUpdate;
}
if (type.prototype && type.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
function checkClassInstance(workInProgress: Fiber) {
const instance = workInProgress.stateNode;
const type = workInProgress.type;
if (__DEV__) {
const name = getComponentName(workInProgress);
const renderPresent = instance.render;
if (!renderPresent) {
if (type.prototype && typeof type.prototype.render === 'function') {
warning(
false,
'%s(...): No `render` method found on the returned component ' +
'instance: did you accidentally return an object from the constructor?',
name,
);
} else {
warning(
false,
'%s(...): No `render` method found on the returned component ' +
'instance: you may have forgotten to define `render`.',
name,
);
}
}
const noGetInitialStateOnES6 =
!instance.getInitialState ||
instance.getInitialState.isReactClassApproved ||
instance.state;
warning(
noGetInitialStateOnES6,
'getInitialState was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Did you mean to define a state property instead?',
name,
);
const noGetDefaultPropsOnES6 =
!instance.getDefaultProps ||
instance.getDefaultProps.isReactClassApproved;
warning(
noGetDefaultPropsOnES6,
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Use a static property to define defaultProps instead.',
name,
);
const noInstancePropTypes = !instance.propTypes;
warning(
noInstancePropTypes,
'propTypes was defined as an instance property on %s. Use a static ' +
'property to define propTypes instead.',
name,
);
const noInstanceContextTypes = !instance.contextTypes;
warning(
noInstanceContextTypes,
'contextTypes was defined as an instance property on %s. Use a static ' +
'property to define contextTypes instead.',
name,
);
const noComponentShouldUpdate =
typeof instance.componentShouldUpdate !== 'function';
warning(
noComponentShouldUpdate,
'%s has a method called ' +
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
'The name is phrased as a question because the function is ' +
'expected to return a value.',
name,
);
if (
type.prototype &&
type.prototype.isPureReactComponent &&
typeof instance.shouldComponentUpdate !== 'undefined'
) {
warning(
false,
'%s has a method called shouldComponentUpdate(). ' +
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
'Please extend React.Component if shouldComponentUpdate is used.',
getComponentName(workInProgress) || 'A pure component',
);
}
const noComponentDidUnmount =
typeof instance.componentDidUnmount !== 'function';
warning(
noComponentDidUnmount,
'%s has a method called ' +
'componentDidUnmount(). But there is no such lifecycle method. ' +
'Did you mean componentWillUnmount()?',
name,
);
const noComponentDidReceiveProps =
typeof instance.componentDidReceiveProps !== 'function';
warning(
noComponentDidReceiveProps,
'%s has a method called ' +
'componentDidReceiveProps(). But there is no such lifecycle method. ' +
'If you meant to update the state in response to changing props, ' +
'use componentWillReceiveProps(). If you meant to fetch data or ' +
'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
name,
);
const noComponentWillRecieveProps =
typeof instance.componentWillRecieveProps !== 'function';
warning(
noComponentWillRecieveProps,
'%s has a method called ' +
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
name,
);
const noUnsafeComponentWillRecieveProps =
typeof instance.UNSAFE_componentWillRecieveProps !== 'function';
warning(
noUnsafeComponentWillRecieveProps,
'%s has a method called ' +
'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
name,
);
const hasMutatedProps = instance.props !== workInProgress.pendingProps;
warning(
instance.props === undefined || !hasMutatedProps,
'%s(...): When calling super() in `%s`, make sure to pass ' +
"up the same props that your component's constructor was passed.",
name,
name,
);
const noInstanceDefaultProps = !instance.defaultProps;
warning(
noInstanceDefaultProps,
'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
' Instead, define defaultProps as a static property on %s.',
name,
name,
);
}
const state = instance.state;
if (state && (typeof state !== 'object' || isArray(state))) {
warning(
false,
'%s.state: must be set to an object or null',
getComponentName(workInProgress),
);
}
if (typeof instance.getChildContext === 'function') {
warning(
typeof workInProgress.type.childContextTypes === 'object',
'%s.getChildContext(): childContextTypes must be defined in order to ' +
'use getChildContext().',
getComponentName(workInProgress),
);
}
}
function resetInputPointers(workInProgress: Fiber, instance: any) {
instance.props = workInProgress.memoizedProps;
instance.state = workInProgress.memoizedState;
}
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = updater;
workInProgress.stateNode = instance;
// The instance needs access to the fiber so that it can schedule updates
ReactInstanceMap.set(instance, workInProgress);
if (__DEV__) {
instance._reactInternalInstance = fakeInternalInstance;
}
}
function constructClassInstance(workInProgress: Fiber, props: any): any {
const ctor = workInProgress.type;
const unmaskedContext = getUnmaskedContext(workInProgress);
const needsContext = isContextConsumer(workInProgress);
const context = needsContext
? getMaskedContext(workInProgress, unmaskedContext)
: emptyObject;
// Instantiate twice to help detect side-effects.
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.internalContextTag & StrictMode)
) {
new ctor(props, context); // eslint-disable-line no-new
}
const instance = new ctor(props, context);
const state =
instance.state !== null && instance.state !== undefined
? instance.state
: null;
adoptClassInstance(workInProgress, instance);
if (__DEV__) {
if (
typeof ctor.getDerivedStateFromProps === 'function' &&
state === null
) {
const componentName = getComponentName(workInProgress) || 'Unknown';
if (!didWarnAboutUninitializedState[componentName]) {
warning(
false,
'%s: Did not properly initialize state during construction. ' +
'Expected state to be an object, but it was %s.',
componentName,
instance.state === null ? 'null' : 'undefined',
);
didWarnAboutUninitializedState[componentName] = true;
}
}
}
workInProgress.memoizedState = state;
const partialState = callGetDerivedStateFromProps(
workInProgress,
instance,
props,
);
if (partialState !== null && partialState !== undefined) {
// Render-phase updates (like this) should not be added to the update queue,
// So that multiple render passes do not enqueue multiple updates.
// Instead, just synchronously merge the returned state into the instance.
workInProgress.memoizedState = Object.assign(
{},
workInProgress.memoizedState,
partialState,
);
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
if (needsContext) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
function callComponentWillMount(workInProgress, instance) {
startPhaseTimer(workInProgress, 'componentWillMount');
const oldState = instance.state;
if (typeof instance.componentWillMount === 'function') {
instance.componentWillMount();
} else {
instance.UNSAFE_componentWillMount();
}
stopPhaseTimer();
if (oldState !== instance.state) {
if (__DEV__) {
warning(
false,
'%s.componentWillMount(): Assigning directly to this.state is ' +
"deprecated (except inside a component's " +
'constructor). Use setState instead.',
getComponentName(workInProgress),
);
}
updater.enqueueReplaceState(instance, instance.state, null);
}
}
function callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
) {
const oldState = instance.state;
if (typeof instance.componentWillReceiveProps === 'function') {
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
instance.componentWillReceiveProps(newProps, newContext);
stopPhaseTimer();
} else {
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
stopPhaseTimer();
}
if (instance.state !== oldState) {
if (__DEV__) {
const componentName = getComponentName(workInProgress) || 'Component';
if (!didWarnAboutStateAssignmentForComponent[componentName]) {
warning(
false,
'%s.componentWillReceiveProps(): Assigning directly to ' +
"this.state is deprecated (except inside a component's " +
'constructor). Use setState instead.',
componentName,
);
didWarnAboutStateAssignmentForComponent[componentName] = true;
}
}
updater.enqueueReplaceState(instance, instance.state, null);
}
}
function callGetDerivedStateFromProps(
workInProgress: Fiber,
instance: any,
props: any,
) {
const {type} = workInProgress;
if (typeof type.getDerivedStateFromProps === 'function') {
if (__DEV__) {
if (
typeof instance.componentWillReceiveProps === 'function' ||
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
) {
const componentName = getComponentName(workInProgress) || 'Unknown';
if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
warning(
false,
'%s: Defines both componentWillReceiveProps() and static ' +
'getDerivedStateFromProps() methods. We recommend using ' +
'only getDerivedStateFromProps().',
componentName,
);
didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
}
}
}
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.internalContextTag & StrictMode)
) {
// Invoke method an extra time to help detect side-effects.
type.getDerivedStateFromProps.call(
null,
props,
workInProgress.memoizedState,
);
}
const partialState = type.getDerivedStateFromProps.call(
null,
props,
workInProgress.memoizedState,
);
if (__DEV__) {
if (partialState === undefined) {
const componentName = getComponentName(workInProgress) || 'Unknown';
if (!didWarnAboutUndefinedDerivedState[componentName]) {
warning(
false,
'%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
'You have returned undefined.',
componentName,
);
didWarnAboutUndefinedDerivedState[componentName] = componentName;
}
}
}
return partialState;
}
}
// Invokes the mount life-cycles on a previously never rendered instance.
function mountClassInstance(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): void {
const current = workInProgress.alternate;
if (__DEV__) {
checkClassInstance(workInProgress);
}
const instance = workInProgress.stateNode;
const props = workInProgress.pendingProps;
const unmaskedContext = getUnmaskedContext(workInProgress);
instance.props = props;
instance.state = workInProgress.memoizedState;
instance.refs = emptyObject;
instance.context = getMaskedContext(workInProgress, unmaskedContext);
if (workInProgress.type != null && workInProgress.type.prototype != null) {
const prototype = workInProgress.type.prototype;
if (enableAsyncSubtreeAPI) {
if (prototype.unstable_isAsyncReactComponent === true) {
workInProgress.internalContextTag |= AsyncUpdates;
workInProgress.internalContextTag |= StrictMode;
}
}
}
if (__DEV__) {
if (workInProgress.internalContextTag & StrictMode) {
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
workInProgress,
instance,
);
}
if (warnAboutDeprecatedLifecycles) {
ReactStrictModeWarnings.recordDeprecationWarnings(
workInProgress,
instance,
);
}
}
if (
typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function'
) {
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
instance.state = processUpdateQueue(
current,
workInProgress,
updateQueue,
instance,
props,
renderExpirationTime,
);
}
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
// Called on a preexisting class instance. Returns false if a resumed render
// could be reused.
// function resumeMountClassInstance(
// workInProgress: Fiber,
// priorityLevel: PriorityLevel,
// ): boolean {
// const instance = workInProgress.stateNode;
// resetInputPointers(workInProgress, instance);
// let newState = workInProgress.memoizedState;
// let newProps = workInProgress.pendingProps;
// if (!newProps) {
// // If there isn't any new props, then we'll reuse the memoized props.
// // This could be from already completed work.
// newProps = workInProgress.memoizedProps;
// invariant(
// newProps != null,
// 'There should always be pending or memoized props. This error is ' +
// 'likely caused by a bug in React. Please file an issue.',
// );
// }
// const newUnmaskedContext = getUnmaskedContext(workInProgress);
// const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
// const oldContext = instance.context;
// const oldProps = workInProgress.memoizedProps;
// if (
// typeof instance.componentWillReceiveProps === 'function' &&
// (oldProps !== newProps || oldContext !== newContext)
// ) {
// callComponentWillReceiveProps(
// workInProgress,
// instance,
// newProps,
// newContext,
// );
// }
// // Process the update queue before calling shouldComponentUpdate
// const updateQueue = workInProgress.updateQueue;
// if (updateQueue !== null) {
// newState = processUpdateQueue(
// workInProgress,
// updateQueue,
// instance,
// newState,
// newProps,
// priorityLevel,
// );
// }
// // TODO: Should we deal with a setState that happened after the last
// // componentWillMount and before this componentWillMount? Probably
// // unsupported anyway.
// if (
// !checkShouldComponentUpdate(
// workInProgress,
// workInProgress.memoizedProps,
// newProps,
// workInProgress.memoizedState,
// newState,
// newContext,
// )
// ) {
// // Update the existing instance's state, props, and context pointers even
// // though we're bailing out.
// instance.props = newProps;
// instance.state = newState;
// instance.context = newContext;
// return false;
// }
// // Update the input pointers now so that they are correct when we call
// // componentWillMount
// instance.props = newProps;
// instance.state = newState;
// instance.context = newContext;
// if (typeof instance.componentWillMount === 'function') {
// callComponentWillMount(workInProgress, instance);
// // componentWillMount may have called setState. Process the update queue.
// const newUpdateQueue = workInProgress.updateQueue;
// if (newUpdateQueue !== null) {
// newState = processUpdateQueue(
// workInProgress,
// newUpdateQueue,
// instance,
// newState,
// newProps,
// priorityLevel,
// );
// }
// }
// if (typeof instance.componentDidMount === 'function') {
// workInProgress.effectTag |= Update;
// }
// instance.state = newState;
// return true;
// }
// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): boolean {
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);
const oldProps = workInProgress.memoizedProps;
const newProps = workInProgress.pendingProps;
const oldContext = instance.context;
const newUnmaskedContext = getUnmaskedContext(workInProgress);
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
if (
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function') &&
(oldProps !== newProps || oldContext !== newContext)
) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
);
}
let partialState;
if (oldProps !== newProps) {
partialState = callGetDerivedStateFromProps(
workInProgress,
instance,
newProps,
);
}
// Compute the next state using the memoized state and the update queue.
const oldState = workInProgress.memoizedState;
// TODO: Previous state can be null.
let newState;
if (workInProgress.updateQueue !== null) {
newState = processUpdateQueue(
current,
workInProgress,
workInProgress.updateQueue,
instance,
newProps,
renderExpirationTime,
);
} else {
newState = oldState;
}
if (partialState !== null && partialState !== undefined) {
// Render-phase updates (like this) should not be added to the update queue,
// So that multiple render passes do not enqueue multiple updates.
// Instead, just synchronously merge the returned state into the instance.
newState =
newState === null || newState === undefined
? partialState
: Object.assign({}, newState, partialState);
}
if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!(
workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate
)
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
return false;
}
const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);
if (shouldUpdate) {
if (
typeof instance.UNSAFE_componentWillUpdate === 'function' ||
typeof instance.componentWillUpdate === 'function'
) {
if (typeof instance.componentWillUpdate === 'function') {
startPhaseTimer(workInProgress, 'componentWillUpdate');
instance.componentWillUpdate(newProps, newState, newContext);
stopPhaseTimer();
} else {
startPhaseTimer(workInProgress, 'componentWillUpdate');
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
stopPhaseTimer();
}
}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
memoizeProps(workInProgress, newProps);
memoizeState(workInProgress, newState);
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = newContext;
return shouldUpdate;
}
return {
adoptClassInstance,
callGetDerivedStateFromProps,
constructClassInstance,
mountClassInstance,
// resumeMountClassInstance,
updateClassInstance,
};
}