react/packages/react-reconciler/src/ReactFiberClassComponent.js

722 lines
23 KiB
JavaScript
Raw Normal View History

/**
* 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
*/
Drop Haste (#11303) * Use relative paths in packages/react * Use relative paths in packages/react-art * Use relative paths in packages/react-cs * Use relative paths in other packages * Fix as many issues as I can This uncovered an interesting problem where ./b from package/src/a would resolve to a different instantiation of package/src/b in Jest. Either this is a showstopper or we can solve it by completely fobbidding remaining /src/. * Fix all tests It seems we can't use relative requires in tests anymore. Otherwise Jest becomes confused between real file and symlink. https://github.com/facebook/jest/issues/3830 This seems bad... Except that we already *don't* want people to create tests that import individual source files. All existing cases of us doing so are actually TODOs waiting to be fixed. So perhaps this requirement isn't too bad because it makes bad code looks bad. Of course, if we go with this, we'll have to lint against relative requires in tests. It also makes moving things more painful. * Prettier * Remove @providesModule * Fix remaining Haste imports I missed earlier * Fix up paths to reflect new flat structure * Fix Flow * Fix CJS and UMD builds * Fix FB bundles * Fix RN bundles * Prettier * Fix lint * Fix warning printing and error codes * Fix buggy return * Fix lint and Flow * Use Yarn on CI * Unbreak Jest * Fix lint * Fix aliased originals getting included in DEV Shouldn't affect correctness (they were ignored) but fixes DEV size regression. * Record sizes * Fix weird version in package.json * Tweak bundle labels * Get rid of output option by introducing react-dom/server.node * Reconciler should depend on prop-types * Update sizes last time
2017-10-25 07:55:00 +08:00
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import {Update} from 'shared/ReactTypeOfSideEffect';
import {
debugRenderPhaseSideEffects,
enableAsyncSubtreeAPI,
} from 'shared/ReactFeatureFlags';
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} from './ReactTypeOfInternalContext';
import {
cacheContext,
2016-11-12 05:14:20 +08:00
getMaskedContext,
getUnmaskedContext,
isContextConsumer,
} from './ReactFiberContext';
import {
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
insertUpdateIntoFiber,
processUpdateQueue,
} from './ReactFiberUpdateQueue';
import {hasContextChanged} from './ReactFiberContext';
const fakeInternalInstance = {};
const isArray = Array.isArray;
let didWarnAboutStateAssignmentForComponent;
let warnOnInvalidCallback;
if (__DEV__) {
didWarnAboutStateAssignmentForComponent = {};
warnOnInvalidCallback = function(callback: mixed, callerName: string) {
warning(
callback === null || typeof callback === 'function',
'%s(...): Expected the last optional `callback` argument to be a ' +
2017-03-14 08:05:18 +08:00
'function. Instead received: %s.',
callerName,
2017-03-14 08:05:18 +08:00
callback,
);
};
// 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(
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
2017-03-14 08:05:18 +08:00
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');
}
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
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');
}
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
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');
}
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: null,
callback,
isReplace: false,
isForced: true,
nextCallback: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
};
2017-03-14 08:05:18 +08:00
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');
2017-03-14 08:05:18 +08:00
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
newContext,
);
stopPhaseTimer();
// Simulate an async bailout/interruption by invoking lifecycle twice.
if (debugRenderPhaseSideEffects) {
instance.shouldComponentUpdate(newProps, newState, newContext);
}
if (__DEV__) {
warning(
shouldUpdate !== undefined,
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
2017-03-14 08:05:18 +08:00
'boolean value. Make sure to return true or false.',
getComponentName(workInProgress) || 'Unknown',
);
}
return shouldUpdate;
}
if (type.prototype && type.prototype.isPureReactComponent) {
2017-04-21 02:18:33 +08:00
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
2016-11-12 05:17:38 +08:00
function checkClassInstance(workInProgress: Fiber) {
const instance = workInProgress.stateNode;
const type = workInProgress.type;
if (__DEV__) {
2016-11-12 05:14:20 +08:00
const name = getComponentName(workInProgress);
2016-11-12 05:17:38 +08:00
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,
);
}
}
2017-04-21 02:18:33 +08:00
const noGetInitialStateOnES6 =
!instance.getInitialState ||
instance.getInitialState.isReactClassApproved ||
2017-03-14 08:05:18 +08:00
instance.state;
warning(
noGetInitialStateOnES6,
'getInitialState was defined on %s, a plain JavaScript class. ' +
2017-03-14 08:05:18 +08:00
'This is only supported for classes created using React.createClass. ' +
'Did you mean to define a state property instead?',
name,
);
2017-04-21 02:18:33 +08:00
const noGetDefaultPropsOnES6 =
!instance.getDefaultProps ||
2017-03-14 08:05:18 +08:00
instance.getDefaultProps.isReactClassApproved;
warning(
noGetDefaultPropsOnES6,
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
2017-03-14 08:05:18 +08:00
'This is only supported for classes created using React.createClass. ' +
'Use a static property to define defaultProps instead.',
name,
);
2016-11-12 05:17:38 +08:00
const noInstancePropTypes = !instance.propTypes;
warning(
noInstancePropTypes,
'propTypes was defined as an instance property on %s. Use a static ' +
2017-03-14 08:05:18 +08:00
'property to define propTypes instead.',
name,
);
2016-11-12 05:17:38 +08:00
const noInstanceContextTypes = !instance.contextTypes;
warning(
noInstanceContextTypes,
'contextTypes was defined as an instance property on %s. Use a static ' +
2017-03-14 08:05:18 +08:00
'property to define contextTypes instead.',
name,
);
2017-04-21 02:18:33 +08:00
const noComponentShouldUpdate =
typeof instance.componentShouldUpdate !== 'function';
warning(
noComponentShouldUpdate,
'%s has a method called ' +
2017-03-14 08:05:18 +08:00
'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',
);
}
2017-04-21 02:18:33 +08:00
const noComponentDidUnmount =
typeof instance.componentDidUnmount !== 'function';
warning(
noComponentDidUnmount,
'%s has a method called ' +
2017-03-14 08:05:18 +08:00
'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,
);
2017-04-21 02:18:33 +08:00
const noComponentWillRecieveProps =
typeof instance.componentWillRecieveProps !== 'function';
warning(
noComponentWillRecieveProps,
'%s has a method called ' +
2017-03-14 08:05:18 +08:00
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
name,
);
[Fiber] Fix some of the warnings (#8570) * Implement component stack for some warnings in Fiber * Keep Fiber debug source up to date When an element changes, we should copy the source and owner again. Otherwise they can get stale since we're not reading them from the element. * Remove outdated TODOs from tests * Explicitly specify Fiber types to include in the stack Fixes an accidental omission when both source and owner are null but displayName exists. * Fix mised Stack+Fiber test to not expect extra warnings When we're in Fiber mode we don't actually expect that warning being printed. * Warn on passing different props to super() * Implement duplicate key warnings We keep known keys in a set in development. There is an annoying special case where we know we'll check it again because we break out of the loop early. One test in the tree hook regresses to the failing state because it checks that the tree hook works without a Set available, but we started using Set in this code. It is not essential and we can clean this up later when we decide how to deal with polyfills. * Move ReactTypeOfWork to src/shared It needs to be available both to Fiber and Isomorphic because the tree hook lives in Isomorphic but pretty-prints Fiber stack. * Add dev-only ReactDebugCurrentFiber for warnings The goal is to use ReactCurrentOwner less and rely on ReactDebugCurrentFiber for warning owner name and stack. * Make Stack invariant messages more consistent Fiber used a helper so two tests had the same phrasing. Stack also used a helper for most invariants but hardcoded a different phrase in one place. I changed that invariant message to use a helper which made it consistent with what it prints in Fiber. * Make CSSPropertyOperations use getCurrentFiberOwnerName() This gets mount-time CSS warnings to be printed. However update-time warnings are currently ignored because current fiber is not yet available during the commit phase. We also regress on HostOperation hook tests but this doesn't matter because it's only used by ReactPerf and it doesn't work with Fiber yet anyway. We'll have to think more about it later. * Set ReactDebugCurrentFiber during the commit phase This makes it available during updates, fixing the last failing test in CSSPropertyOperations. * Add DOM warnings by calling hooks directly It is not clear if the old hook system is worth it in its generic incarnation. For now I am just hooking it up to the DOMFiber renderer directly. * Add client-side counterparts for some warning tests This helps us track which warnings are really failing in Fiber, and which ones depend on SSR.
2016-12-16 00:36:58 +08:00
const hasMutatedProps = instance.props !== workInProgress.pendingProps;
warning(
instance.props === undefined || !hasMutatedProps,
'%s(...): When calling super() in `%s`, make sure to pass ' +
2017-03-14 08:05:18 +08:00
"up the same props that your component's constructor was passed.",
name,
[Fiber] Fix some of the warnings (#8570) * Implement component stack for some warnings in Fiber * Keep Fiber debug source up to date When an element changes, we should copy the source and owner again. Otherwise they can get stale since we're not reading them from the element. * Remove outdated TODOs from tests * Explicitly specify Fiber types to include in the stack Fixes an accidental omission when both source and owner are null but displayName exists. * Fix mised Stack+Fiber test to not expect extra warnings When we're in Fiber mode we don't actually expect that warning being printed. * Warn on passing different props to super() * Implement duplicate key warnings We keep known keys in a set in development. There is an annoying special case where we know we'll check it again because we break out of the loop early. One test in the tree hook regresses to the failing state because it checks that the tree hook works without a Set available, but we started using Set in this code. It is not essential and we can clean this up later when we decide how to deal with polyfills. * Move ReactTypeOfWork to src/shared It needs to be available both to Fiber and Isomorphic because the tree hook lives in Isomorphic but pretty-prints Fiber stack. * Add dev-only ReactDebugCurrentFiber for warnings The goal is to use ReactCurrentOwner less and rely on ReactDebugCurrentFiber for warning owner name and stack. * Make Stack invariant messages more consistent Fiber used a helper so two tests had the same phrasing. Stack also used a helper for most invariants but hardcoded a different phrase in one place. I changed that invariant message to use a helper which made it consistent with what it prints in Fiber. * Make CSSPropertyOperations use getCurrentFiberOwnerName() This gets mount-time CSS warnings to be printed. However update-time warnings are currently ignored because current fiber is not yet available during the commit phase. We also regress on HostOperation hook tests but this doesn't matter because it's only used by ReactPerf and it doesn't work with Fiber yet anyway. We'll have to think more about it later. * Set ReactDebugCurrentFiber during the commit phase This makes it available during updates, fixing the last failing test in CSSPropertyOperations. * Add DOM warnings by calling hooks directly It is not clear if the old hook system is worth it in its generic incarnation. For now I am just hooking it up to the DOMFiber renderer directly. * Add client-side counterparts for some warning tests This helps us track which warnings are really failing in Fiber, and which ones depend on SSR.
2016-12-16 00:36:58 +08:00
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,
);
}
2016-11-12 05:17:38 +08:00
const state = instance.state;
if (state && (typeof state !== 'object' || isArray(state))) {
warning(
false,
'%s.state: must be set to an object or null',
2017-03-14 08:05:18 +08:00
getComponentName(workInProgress),
2016-11-12 05:14:20 +08:00
);
}
2016-11-12 05:17:38 +08:00
if (typeof instance.getChildContext === 'function') {
warning(
2016-11-12 05:14:20 +08:00
typeof workInProgress.type.childContextTypes === 'object',
'%s.getChildContext(): childContextTypes must be defined in order to ' +
2017-03-14 08:05:18 +08:00
'use getChildContext().',
getComponentName(workInProgress),
);
}
}
2017-03-14 08:05:18 +08:00
function resetInputPointers(workInProgress: Fiber, instance: any) {
instance.props = workInProgress.memoizedProps;
instance.state = workInProgress.memoizedState;
}
2017-03-14 08:05:18 +08:00
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);
2017-03-14 08:05:18 +08:00
const context = needsContext
? getMaskedContext(workInProgress, unmaskedContext)
: emptyObject;
2016-11-12 05:14:20 +08:00
const instance = new ctor(props, context);
adoptClassInstance(workInProgress, instance);
// 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;
instance.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,
) {
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
const oldState = instance.state;
instance.componentWillReceiveProps(newProps, newContext);
stopPhaseTimer();
// Simulate an async bailout/interruption by invoking lifecycle twice.
if (debugRenderPhaseSideEffects) {
instance.componentWillReceiveProps(newProps, newContext);
}
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);
}
}
// Invokes the mount life-cycles on a previously never rendered instance.
2017-03-14 08:05:18 +08:00
function mountClassInstance(
workInProgress: Fiber,
Expiration times (#10426) * [Work-in-progress] Assign expiration times to updates An expiration time represents a time in the future by which an update should flush. The priority of the update is related to the difference between the current clock time and the expiration time. This has the effect of increasing the priority of updates as time progresses, to prevent starvation. This lays the initial groundwork for expiration times without changing any behavior. Future commits will replace work priority with expiration times. * Replace pendingWorkPriority with expiration times Instead of a priority, a fiber has an expiration time that represents a point in the future by which it should render. Pending updates still have priorities so that they can be coalesced. We use a host config method to read the current time. This commit implements everything except that method, which currently returns a constant value. So this just proves that expiration times work the same as priorities when time is frozen. Subsequent commits will show the effect of advancing time. * Triangle Demo should use a class shouldComponentUpdate was removed from functional components. Running the demo shows, now that expiration is enabled, the demo does not starve. (Still won't run smoothly until we add back the ability to resume interrupted work.) * Use a magic value for task expiration time There are a few cases related to sync mode where we need to distinguish between work that is scheduled as task and work that is treated like task because it expires. For example, batchedUpdates. We don't want to perform any work until the end of the batch, regardless of how much time has elapsed. * Use current time to calculate expiration time * Add unit tests for expiration and coalescing * Delete unnecessary abstraction * Move performance.now polyfill to ReactDOMFrameScheduling * Add expiration to fuzz tester * Expiration nits - Rename Done -> NoWork - Use max int32 instead of max safe int - Use bitwise operations instead of Math functions
2017-10-10 09:39:53 +08:00
renderExpirationTime: ExpirationTime,
2017-03-14 08:05:18 +08:00
): void {
const current = workInProgress.alternate;
if (__DEV__) {
checkClassInstance(workInProgress);
}
const instance = workInProgress.stateNode;
const state = instance.state || null;
const props = workInProgress.pendingProps;
const unmaskedContext = getUnmaskedContext(workInProgress);
instance.props = props;
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
instance.state = workInProgress.memoizedState = state;
instance.refs = emptyObject;
instance.context = getMaskedContext(workInProgress, unmaskedContext);
if (
enableAsyncSubtreeAPI &&
workInProgress.type != null &&
workInProgress.type.prototype != null &&
workInProgress.type.prototype.unstable_isAsyncReactComponent === true
) {
workInProgress.internalContextTag |= AsyncUpdates;
}
if (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) {
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
instance.state = processUpdateQueue(
current,
workInProgress,
updateQueue,
instance,
props,
Expiration times (#10426) * [Work-in-progress] Assign expiration times to updates An expiration time represents a time in the future by which an update should flush. The priority of the update is related to the difference between the current clock time and the expiration time. This has the effect of increasing the priority of updates as time progresses, to prevent starvation. This lays the initial groundwork for expiration times without changing any behavior. Future commits will replace work priority with expiration times. * Replace pendingWorkPriority with expiration times Instead of a priority, a fiber has an expiration time that represents a point in the future by which it should render. Pending updates still have priorities so that they can be coalesced. We use a host config method to read the current time. This commit implements everything except that method, which currently returns a constant value. So this just proves that expiration times work the same as priorities when time is frozen. Subsequent commits will show the effect of advancing time. * Triangle Demo should use a class shouldComponentUpdate was removed from functional components. Running the demo shows, now that expiration is enabled, the demo does not starve. (Still won't run smoothly until we add back the ability to resume interrupted work.) * Use a magic value for task expiration time There are a few cases related to sync mode where we need to distinguish between work that is scheduled as task and work that is treated like task because it expires. For example, batchedUpdates. We don't want to perform any work until the end of the batch, regardless of how much time has elapsed. * Use current time to calculate expiration time * Add unit tests for expiration and coalescing * Delete unnecessary abstraction * Move performance.now polyfill to ReactDOMFrameScheduling * Add expiration to fuzz tester * Expiration nits - Rename Done -> NoWork - Use max int32 instead of max safe int - Use bitwise operations instead of Math functions
2017-10-10 09:39:53 +08:00
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) {
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
// 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) {
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
// 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.
2017-03-14 08:05:18 +08:00
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
Expiration times (#10426) * [Work-in-progress] Assign expiration times to updates An expiration time represents a time in the future by which an update should flush. The priority of the update is related to the difference between the current clock time and the expiration time. This has the effect of increasing the priority of updates as time progresses, to prevent starvation. This lays the initial groundwork for expiration times without changing any behavior. Future commits will replace work priority with expiration times. * Replace pendingWorkPriority with expiration times Instead of a priority, a fiber has an expiration time that represents a point in the future by which it should render. Pending updates still have priorities so that they can be coalesced. We use a host config method to read the current time. This commit implements everything except that method, which currently returns a constant value. So this just proves that expiration times work the same as priorities when time is frozen. Subsequent commits will show the effect of advancing time. * Triangle Demo should use a class shouldComponentUpdate was removed from functional components. Running the demo shows, now that expiration is enabled, the demo does not starve. (Still won't run smoothly until we add back the ability to resume interrupted work.) * Use a magic value for task expiration time There are a few cases related to sync mode where we need to distinguish between work that is scheduled as task and work that is treated like task because it expires. For example, batchedUpdates. We don't want to perform any work until the end of the batch, regardless of how much time has elapsed. * Use current time to calculate expiration time * Add unit tests for expiration and coalescing * Delete unnecessary abstraction * Move performance.now polyfill to ReactDOMFrameScheduling * Add expiration to fuzz tester * Expiration nits - Rename Done -> NoWork - Use max int32 instead of max safe int - Use bitwise operations instead of Math functions
2017-10-10 09:39:53 +08:00
renderExpirationTime: ExpirationTime,
2017-03-14 08:05:18 +08:00
): boolean {
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);
const oldProps = workInProgress.memoizedProps;
const newProps = workInProgress.pendingProps;
2016-11-12 05:14:20 +08:00
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.componentWillReceiveProps === 'function' &&
(oldProps !== newProps || oldContext !== newContext)
) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
);
}
// Compute the next state using the memoized state and the update queue.
2016-11-12 05:17:38 +08:00
const oldState = workInProgress.memoizedState;
// TODO: Previous state can be null.
let newState;
if (workInProgress.updateQueue !== null) {
Deterministic updates (#10715) * Deterministic updates High priority updates typically require less work to render than low priority ones. It's beneficial to flush those first, in their own batch, before working on more expensive low priority ones. We do this even if a high priority is scheduled after a low priority one. However, we don't want this reordering of updates to affect the terminal state. State should be deterministic: once all work has been flushed, the final state should be the same regardless of how they were scheduled. To get both properties, we store updates on the queue in insertion order instead of priority order (always append). Then, when processing the queue, we skip over updates with insufficient priority. Instead of removing updates from the queue right after processing them, we only remove them if there are no unprocessed updates before it in the list. This means that updates may be processed more than once. As a bonus, the new implementation is simpler and requires less code. * Fix ceiling function Mixed up the operators. * Remove addUpdate, addReplaceState, et al These functions don't really do anything. Simpler to use a single insertUpdateIntoFiber function. Also splits scheduleUpdate into two functions: - scheduleWork traverses a fiber's ancestor path and updates their expiration times. - scheduleUpdate inserts an update into a fiber's update queue, then calls scheduleWork. * Remove getExpirationTime The last remaining use for getExpirationTime was for top-level async updates. I moved that check to scheduleUpdate instead. * Move UpdateQueue insertions back to class module Moves UpdateQueue related functions out of the scheduler and back into the class component module. It's a bit awkward that now we need to pass around createUpdateExpirationForFiber, too. But we can still do without addUpdate, replaceUpdate, et al. * Store callbacks as an array of Updates Simpler this way. Also moves commitCallbacks back to UpdateQueue module. * beginUpdateQueue -> processUpdateQueue * Updates should never have an expiration of NoWork * Rename expiration related functions * Fix update queue Flow types Gets rid of an unneccessary null check
2017-10-14 08:21:25 +08:00
newState = processUpdateQueue(
current,
workInProgress,
workInProgress.updateQueue,
instance,
newProps,
Expiration times (#10426) * [Work-in-progress] Assign expiration times to updates An expiration time represents a time in the future by which an update should flush. The priority of the update is related to the difference between the current clock time and the expiration time. This has the effect of increasing the priority of updates as time progresses, to prevent starvation. This lays the initial groundwork for expiration times without changing any behavior. Future commits will replace work priority with expiration times. * Replace pendingWorkPriority with expiration times Instead of a priority, a fiber has an expiration time that represents a point in the future by which it should render. Pending updates still have priorities so that they can be coalesced. We use a host config method to read the current time. This commit implements everything except that method, which currently returns a constant value. So this just proves that expiration times work the same as priorities when time is frozen. Subsequent commits will show the effect of advancing time. * Triangle Demo should use a class shouldComponentUpdate was removed from functional components. Running the demo shows, now that expiration is enabled, the demo does not starve. (Still won't run smoothly until we add back the ability to resume interrupted work.) * Use a magic value for task expiration time There are a few cases related to sync mode where we need to distinguish between work that is scheduled as task and work that is treated like task because it expires. For example, batchedUpdates. We don't want to perform any work until the end of the batch, regardless of how much time has elapsed. * Use current time to calculate expiration time * Add unit tests for expiration and coalescing * Delete unnecessary abstraction * Move performance.now polyfill to ReactDOMFrameScheduling * Add expiration to fuzz tester * Expiration nits - Rename Done -> NoWork - Use max int32 instead of max safe int - Use bitwise operations instead of Math functions
2017-10-10 09:39:53 +08:00
renderExpirationTime,
);
} else {
2016-11-12 05:17:38 +08:00
newState = oldState;
}
2017-03-14 08:05:18 +08:00
if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!(
workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate
)
2017-03-14 08:05:18 +08:00
) {
// 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') {
2017-03-14 08:05:18 +08:00
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
return false;
}
const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
2016-11-12 05:14:20 +08:00
newState,
2017-03-14 08:05:18 +08:00
newContext,
);
if (shouldUpdate) {
if (typeof instance.componentWillUpdate === 'function') {
startPhaseTimer(workInProgress, 'componentWillUpdate');
instance.componentWillUpdate(newProps, newState, newContext);
stopPhaseTimer();
// Simulate an async bailout/interruption by invoking lifecycle twice.
if (debugRenderPhaseSideEffects) {
instance.componentWillUpdate(newProps, newState, newContext);
}
}
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') {
2017-03-14 08:05:18 +08:00
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;
2016-11-12 05:14:20 +08:00
instance.context = newContext;
return shouldUpdate;
}
return {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
// resumeMountClassInstance,
updateClassInstance,
};
}