2016-10-12 01:43:47 +08:00
|
|
|
/**
|
2017-09-25 04:48:13 +08:00
|
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
2016-10-12 01:43:47 +08:00
|
|
|
*
|
2017-09-25 04:48:13 +08:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2016-10-12 01:43:47 +08:00
|
|
|
*
|
|
|
|
|
* @flow
|
|
|
|
|
*/
|
|
|
|
|
|
2017-10-25 07:55:00 +08:00
|
|
|
import type {Fiber} from './ReactFiber';
|
|
|
|
|
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
2016-10-12 01:43:47 +08:00
|
|
|
|
2017-11-03 03:50:03 +08:00
|
|
|
import {Update} from 'shared/ReactTypeOfSideEffect';
|
2017-11-18 02:49:54 +08:00
|
|
|
import {
|
|
|
|
|
debugRenderPhaseSideEffects,
|
|
|
|
|
enableAsyncSubtreeAPI,
|
|
|
|
|
} from 'shared/ReactFeatureFlags';
|
2017-11-29 00:57:22 +08:00
|
|
|
import {isMounted} from 'react-reconciler/reflection';
|
2017-11-03 03:50:03 +08:00
|
|
|
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';
|
|
|
|
|
|
2017-11-07 00:07:08 +08:00
|
|
|
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
|
2017-11-03 03:50:03 +08:00
|
|
|
import {AsyncUpdates} from './ReactTypeOfInternalContext';
|
|
|
|
|
import {
|
2017-01-10 05:51:03 +08:00
|
|
|
cacheContext,
|
2016-11-12 05:14:20 +08:00
|
|
|
getMaskedContext,
|
2017-01-08 00:53:24 +08:00
|
|
|
getUnmaskedContext,
|
2017-01-10 05:51:03 +08:00
|
|
|
isContextConsumer,
|
2017-11-03 03:50:03 +08:00
|
|
|
} 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,
|
2017-11-03 03:50:03 +08:00
|
|
|
} from './ReactFiberUpdateQueue';
|
|
|
|
|
import {hasContextChanged} from './ReactFiberContext';
|
2016-11-06 02:19:48 +08:00
|
|
|
|
2017-08-14 07:03:31 +08:00
|
|
|
const fakeInternalInstance = {};
|
2016-11-06 02:19:48 +08:00
|
|
|
const isArray = Array.isArray;
|
2016-10-12 01:43:47 +08:00
|
|
|
|
2017-12-05 21:47:57 +08:00
|
|
|
let didWarnAboutStateAssignmentForComponent;
|
|
|
|
|
let warnOnInvalidCallback;
|
|
|
|
|
|
2017-02-10 07:35:59 +08:00
|
|
|
if (__DEV__) {
|
2017-12-05 21:47:57 +08:00
|
|
|
didWarnAboutStateAssignmentForComponent = {};
|
2017-10-26 02:07:54 +08:00
|
|
|
|
2017-12-05 21:47:57 +08:00
|
|
|
warnOnInvalidCallback = function(callback: mixed, callerName: string) {
|
2017-02-10 07:35:59 +08:00
|
|
|
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.',
|
2017-02-10 07:35:59 +08:00
|
|
|
callerName,
|
2017-03-14 08:05:18 +08:00
|
|
|
callback,
|
2017-02-10 07:35:59 +08:00
|
|
|
);
|
|
|
|
|
};
|
2017-08-14 07:03:31 +08:00
|
|
|
|
|
|
|
|
// 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 ' +
|
2017-09-12 05:23:54 +08:00
|
|
|
'to ReactDOM.createPortal).',
|
2017-08-14 07:03:31 +08:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
Object.freeze(fakeInternalInstance);
|
2017-02-10 07:35:59 +08:00
|
|
|
}
|
|
|
|
|
|
2017-11-03 03:50:03 +08:00
|
|
|
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,
|
2016-12-10 07:06:56 +08:00
|
|
|
) {
|
2016-10-12 01:43:47 +08:00
|
|
|
// Class component state updater
|
|
|
|
|
const updater = {
|
2016-10-23 09:35:07 +08:00
|
|
|
isMounted,
|
2016-12-21 05:07:47 +08:00
|
|
|
enqueueSetState(instance, partialState, callback) {
|
2016-10-12 01:43:47 +08:00
|
|
|
const fiber = ReactInstanceMap.get(instance);
|
2017-02-10 07:35:59 +08:00
|
|
|
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);
|
2016-10-12 01:43:47 +08:00
|
|
|
},
|
2016-12-21 05:07:47 +08:00
|
|
|
enqueueReplaceState(instance, state, callback) {
|
2016-10-12 01:43:47 +08:00
|
|
|
const fiber = ReactInstanceMap.get(instance);
|
2017-02-10 07:35:59 +08:00
|
|
|
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);
|
2016-10-12 01:43:47 +08:00
|
|
|
},
|
2016-12-21 05:07:47 +08:00
|
|
|
enqueueForceUpdate(instance, callback) {
|
2016-10-12 01:43:47 +08:00
|
|
|
const fiber = ReactInstanceMap.get(instance);
|
2017-02-10 07:35:59 +08:00
|
|
|
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);
|
2016-10-12 01:43:47 +08:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
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)
|
|
|
|
|
) {
|
2016-12-10 03:44:31 +08:00
|
|
|
// If the workInProgress already has an Update effect, return true
|
2016-10-27 07:56:46 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const instance = workInProgress.stateNode;
|
2017-04-21 22:55:16 +08:00
|
|
|
const type = workInProgress.type;
|
2016-10-27 07:56:46 +08:00
|
|
|
if (typeof instance.shouldComponentUpdate === 'function') {
|
2017-11-07 00:07:08 +08:00
|
|
|
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
|
2017-03-14 08:05:18 +08:00
|
|
|
const shouldUpdate = instance.shouldComponentUpdate(
|
|
|
|
|
newProps,
|
|
|
|
|
newState,
|
|
|
|
|
newContext,
|
|
|
|
|
);
|
2017-11-07 00:07:08 +08:00
|
|
|
stopPhaseTimer();
|
2016-11-10 00:30:41 +08:00
|
|
|
|
2017-11-18 02:49:54 +08:00
|
|
|
// Simulate an async bailout/interruption by invoking lifecycle twice.
|
|
|
|
|
if (debugRenderPhaseSideEffects) {
|
|
|
|
|
instance.shouldComponentUpdate(newProps, newState, newContext);
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-10 00:30:41 +08:00
|
|
|
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',
|
2016-11-10 00:30:41 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shouldUpdate;
|
2016-10-27 07:56:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (type.prototype && type.prototype.isPureReactComponent) {
|
2017-04-21 02:18:33 +08:00
|
|
|
return (
|
|
|
|
|
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
|
|
|
|
|
);
|
2016-10-27 07:56:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-12 05:17:38 +08:00
|
|
|
function checkClassInstance(workInProgress: Fiber) {
|
|
|
|
|
const instance = workInProgress.stateNode;
|
2017-04-21 22:55:16 +08:00
|
|
|
const type = workInProgress.type;
|
2016-11-06 02:19:48 +08:00
|
|
|
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;
|
2017-11-02 05:01:24 +08:00
|
|
|
|
|
|
|
|
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 ||
|
2016-12-18 03:16:27 +08:00
|
|
|
instance.getInitialState.isReactClassApproved ||
|
2017-03-14 08:05:18 +08:00
|
|
|
instance.state;
|
2016-11-06 02:19:48 +08:00
|
|
|
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,
|
2016-11-06 02:19:48 +08:00
|
|
|
);
|
2017-04-21 02:18:33 +08:00
|
|
|
const noGetDefaultPropsOnES6 =
|
|
|
|
|
!instance.getDefaultProps ||
|
2017-03-14 08:05:18 +08:00
|
|
|
instance.getDefaultProps.isReactClassApproved;
|
2016-11-06 02:19:48 +08:00
|
|
|
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-06 02:19:48 +08:00
|
|
|
);
|
2016-11-12 05:17:38 +08:00
|
|
|
const noInstancePropTypes = !instance.propTypes;
|
2016-11-06 02:19:48 +08:00
|
|
|
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.',
|
2016-11-06 02:19:48 +08:00
|
|
|
name,
|
|
|
|
|
);
|
2016-11-12 05:17:38 +08:00
|
|
|
const noInstanceContextTypes = !instance.contextTypes;
|
2016-11-06 02:19:48 +08:00
|
|
|
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.',
|
2016-11-06 02:19:48 +08:00
|
|
|
name,
|
|
|
|
|
);
|
2017-04-21 02:18:33 +08:00
|
|
|
const noComponentShouldUpdate =
|
|
|
|
|
typeof instance.componentShouldUpdate !== 'function';
|
2016-11-06 02:19:48 +08:00
|
|
|
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,
|
2016-11-06 02:19:48 +08:00
|
|
|
);
|
2017-04-21 22:55:16 +08:00
|
|
|
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';
|
2016-11-06 02:19:48 +08:00
|
|
|
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,
|
2016-11-06 02:19:48 +08:00
|
|
|
);
|
2017-11-07 22:00:31 +08:00
|
|
|
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';
|
2016-11-06 02:19:48 +08:00
|
|
|
warning(
|
|
|
|
|
noComponentWillRecieveProps,
|
|
|
|
|
'%s has a method called ' +
|
2017-03-14 08:05:18 +08:00
|
|
|
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
|
|
|
|
|
name,
|
2016-11-06 02:19:48 +08:00
|
|
|
);
|
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,
|
2016-12-16 00:36:58 +08:00
|
|
|
name,
|
|
|
|
|
);
|
2017-04-21 22:46:19 +08:00
|
|
|
const noInstanceDefaultProps = !instance.defaultProps;
|
|
|
|
|
warning(
|
|
|
|
|
noInstanceDefaultProps,
|
2017-05-02 04:51:47 +08:00
|
|
|
'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
|
2017-05-02 02:30:40 +08:00
|
|
|
' Instead, define defaultProps as a static property on %s.',
|
|
|
|
|
name,
|
2017-04-21 22:46:19 +08:00
|
|
|
name,
|
|
|
|
|
);
|
2016-11-06 02:19:48 +08:00
|
|
|
}
|
|
|
|
|
|
2016-11-12 05:17:38 +08:00
|
|
|
const state = instance.state;
|
|
|
|
|
if (state && (typeof state !== 'object' || isArray(state))) {
|
2017-11-23 02:58:53 +08:00
|
|
|
warning(
|
2016-11-06 02:19:48 +08:00
|
|
|
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') {
|
2017-11-23 02:58:53 +08:00
|
|
|
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),
|
2016-11-06 02:19:48 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-14 08:05:18 +08:00
|
|
|
function resetInputPointers(workInProgress: Fiber, instance: any) {
|
2016-12-30 10:09:45 +08:00
|
|
|
instance.props = workInProgress.memoizedProps;
|
|
|
|
|
instance.state = workInProgress.memoizedState;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-14 08:05:18 +08:00
|
|
|
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
|
2016-10-19 17:06:36 +08:00
|
|
|
instance.updater = updater;
|
|
|
|
|
workInProgress.stateNode = instance;
|
2016-10-12 01:43:47 +08:00
|
|
|
// The instance needs access to the fiber so that it can schedule updates
|
|
|
|
|
ReactInstanceMap.set(instance, workInProgress);
|
2017-08-14 07:03:31 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
|
instance._reactInternalInstance = fakeInternalInstance;
|
|
|
|
|
}
|
2016-10-19 17:06:36 +08:00
|
|
|
}
|
|
|
|
|
|
2017-05-02 05:58:44 +08:00
|
|
|
function constructClassInstance(workInProgress: Fiber, props: any): any {
|
2016-10-19 17:06:36 +08:00
|
|
|
const ctor = workInProgress.type;
|
2017-01-08 00:53:24 +08:00
|
|
|
const unmaskedContext = getUnmaskedContext(workInProgress);
|
2017-01-10 05:51:03 +08:00
|
|
|
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);
|
2016-10-19 17:06:36 +08:00
|
|
|
adoptClassInstance(workInProgress, instance);
|
2017-01-08 00:53:24 +08:00
|
|
|
|
|
|
|
|
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
|
|
|
|
// ReactFiberContext usually updates this cache but can't for newly-created instances.
|
2017-01-10 05:51:03 +08:00
|
|
|
if (needsContext) {
|
|
|
|
|
cacheContext(workInProgress, unmaskedContext, context);
|
|
|
|
|
}
|
2017-01-08 00:53:24 +08:00
|
|
|
|
2016-10-19 17:06:36 +08:00
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-06 05:00:59 +08:00
|
|
|
function callComponentWillMount(workInProgress, instance) {
|
2017-11-07 00:07:08 +08:00
|
|
|
startPhaseTimer(workInProgress, 'componentWillMount');
|
2017-05-06 05:00:59 +08:00
|
|
|
const oldState = instance.state;
|
|
|
|
|
instance.componentWillMount();
|
2017-11-07 00:07:08 +08:00
|
|
|
stopPhaseTimer();
|
2017-05-06 05:00:59 +08:00
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
) {
|
2017-11-07 00:07:08 +08:00
|
|
|
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
|
2017-05-06 05:00:59 +08:00
|
|
|
const oldState = instance.state;
|
|
|
|
|
instance.componentWillReceiveProps(newProps, newContext);
|
2017-11-07 00:07:08 +08:00
|
|
|
stopPhaseTimer();
|
2017-05-06 05:00:59 +08:00
|
|
|
|
2017-11-18 02:49:54 +08:00
|
|
|
// Simulate an async bailout/interruption by invoking lifecycle twice.
|
|
|
|
|
if (debugRenderPhaseSideEffects) {
|
|
|
|
|
instance.componentWillReceiveProps(newProps, newContext);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-06 05:00:59 +08:00
|
|
|
if (instance.state !== oldState) {
|
|
|
|
|
if (__DEV__) {
|
2017-10-31 20:35:28 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2017-05-06 05:00:59 +08:00
|
|
|
}
|
|
|
|
|
updater.enqueueReplaceState(instance, instance.state, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-19 17:06:36 +08:00
|
|
|
// Invokes the mount life-cycles on a previously never rendered instance.
|
2017-03-14 08:05:18 +08:00
|
|
|
function mountClassInstance(
|
|
|
|
|
workInProgress: Fiber,
|
2017-10-10 09:39:53 +08:00
|
|
|
renderExpirationTime: ExpirationTime,
|
2017-03-14 08:05:18 +08:00
|
|
|
): void {
|
2017-06-20 05:58:30 +08:00
|
|
|
const current = workInProgress.alternate;
|
|
|
|
|
|
2017-05-02 06:47:18 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
|
checkClassInstance(workInProgress);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-19 17:06:36 +08:00
|
|
|
const instance = workInProgress.stateNode;
|
|
|
|
|
const state = instance.state || null;
|
2017-11-29 08:50:23 +08:00
|
|
|
const props = workInProgress.pendingProps;
|
2017-01-08 00:53:24 +08:00
|
|
|
const unmaskedContext = getUnmaskedContext(workInProgress);
|
|
|
|
|
|
2016-10-19 17:06:36 +08:00
|
|
|
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;
|
2017-02-23 20:58:37 +08:00
|
|
|
instance.refs = emptyObject;
|
2017-01-08 00:53:24 +08:00
|
|
|
instance.context = getMaskedContext(workInProgress, unmaskedContext);
|
2016-10-19 17:06:36 +08:00
|
|
|
|
2017-05-02 04:23:54 +08:00
|
|
|
if (
|
2017-11-06 22:14:48 +08:00
|
|
|
enableAsyncSubtreeAPI &&
|
2017-05-02 04:23:54 +08:00
|
|
|
workInProgress.type != null &&
|
2017-07-21 09:00:53 +08:00
|
|
|
workInProgress.type.prototype != null &&
|
|
|
|
|
workInProgress.type.prototype.unstable_isAsyncReactComponent === true
|
2017-05-02 04:23:54 +08:00
|
|
|
) {
|
|
|
|
|
workInProgress.internalContextTag |= AsyncUpdates;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-19 17:06:36 +08:00
|
|
|
if (typeof instance.componentWillMount === 'function') {
|
2017-05-06 05:00:59 +08:00
|
|
|
callComponentWillMount(workInProgress, instance);
|
2016-10-19 17:06:36 +08:00
|
|
|
// If we had additional state updates during this life-cycle, let's
|
|
|
|
|
// process them now.
|
|
|
|
|
const updateQueue = workInProgress.updateQueue;
|
2017-02-11 09:30:09 +08:00
|
|
|
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(
|
2017-06-20 05:58:30 +08:00
|
|
|
current,
|
2016-12-10 07:06:56 +08:00
|
|
|
workInProgress,
|
|
|
|
|
updateQueue,
|
|
|
|
|
instance,
|
|
|
|
|
props,
|
2017-10-10 09:39:53 +08:00
|
|
|
renderExpirationTime,
|
2016-12-10 07:06:56 +08:00
|
|
|
);
|
2016-10-19 17:06:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
2017-03-04 12:23:09 +08:00
|
|
|
if (typeof instance.componentDidMount === 'function') {
|
|
|
|
|
workInProgress.effectTag |= Update;
|
|
|
|
|
}
|
2016-10-19 17:06:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called on a preexisting class instance. Returns false if a resumed render
|
|
|
|
|
// could be reused.
|
2017-06-20 05:58:30 +08:00
|
|
|
// 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(
|
2017-06-20 05:58:30 +08:00
|
|
|
// 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(
|
2017-06-20 05:58:30 +08:00
|
|
|
// workInProgress,
|
|
|
|
|
// newUpdateQueue,
|
|
|
|
|
// instance,
|
|
|
|
|
// newState,
|
|
|
|
|
// newProps,
|
|
|
|
|
// priorityLevel,
|
|
|
|
|
// );
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if (typeof instance.componentDidMount === 'function') {
|
|
|
|
|
// workInProgress.effectTag |= Update;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// instance.state = newState;
|
|
|
|
|
|
|
|
|
|
// return true;
|
|
|
|
|
// }
|
2016-10-19 17:06:36 +08:00
|
|
|
|
|
|
|
|
// 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,
|
2017-10-10 09:39:53 +08:00
|
|
|
renderExpirationTime: ExpirationTime,
|
2017-03-14 08:05:18 +08:00
|
|
|
): boolean {
|
2016-10-19 17:06:36 +08:00
|
|
|
const instance = workInProgress.stateNode;
|
2016-12-30 10:09:45 +08:00
|
|
|
resetInputPointers(workInProgress, instance);
|
2016-10-19 17:06:36 +08:00
|
|
|
|
2016-12-30 10:09:45 +08:00
|
|
|
const oldProps = workInProgress.memoizedProps;
|
2017-11-29 08:50:23 +08:00
|
|
|
const newProps = workInProgress.pendingProps;
|
2016-11-12 05:14:20 +08:00
|
|
|
const oldContext = instance.context;
|
2017-01-08 00:53:24 +08:00
|
|
|
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
|
|
|
|
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
2016-10-19 17:06:36 +08:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
2017-05-06 05:00:59 +08:00
|
|
|
if (
|
|
|
|
|
typeof instance.componentWillReceiveProps === 'function' &&
|
|
|
|
|
(oldProps !== newProps || oldContext !== newContext)
|
|
|
|
|
) {
|
2017-05-05 06:38:16 +08:00
|
|
|
callComponentWillReceiveProps(
|
|
|
|
|
workInProgress,
|
|
|
|
|
instance,
|
|
|
|
|
newProps,
|
|
|
|
|
newContext,
|
|
|
|
|
);
|
2016-10-19 17:06:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute the next state using the memoized state and the update queue.
|
2016-11-12 05:17:38 +08:00
|
|
|
const oldState = workInProgress.memoizedState;
|
2016-10-19 17:06:36 +08:00
|
|
|
// TODO: Previous state can be null.
|
|
|
|
|
let newState;
|
2017-06-20 05:58:30 +08:00
|
|
|
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(
|
2017-06-20 05:58:30 +08:00
|
|
|
current,
|
2016-12-10 07:06:56 +08:00
|
|
|
workInProgress,
|
2017-06-20 05:58:30 +08:00
|
|
|
workInProgress.updateQueue,
|
2016-12-10 07:06:56 +08:00
|
|
|
instance,
|
|
|
|
|
newProps,
|
2017-10-10 09:39:53 +08:00
|
|
|
renderExpirationTime,
|
2016-12-10 07:06:56 +08:00
|
|
|
);
|
2016-10-19 17:06:36 +08:00
|
|
|
} else {
|
2016-11-12 05:17:38 +08:00
|
|
|
newState = oldState;
|
2016-10-19 17:06:36 +08:00
|
|
|
}
|
|
|
|
|
|
2017-03-14 08:05:18 +08:00
|
|
|
if (
|
|
|
|
|
oldProps === newProps &&
|
|
|
|
|
oldState === newState &&
|
|
|
|
|
!hasContextChanged() &&
|
2017-11-08 02:09:33 +08:00
|
|
|
!(
|
|
|
|
|
workInProgress.updateQueue !== null &&
|
|
|
|
|
workInProgress.updateQueue.hasForceUpdate
|
|
|
|
|
)
|
2017-03-14 08:05:18 +08:00
|
|
|
) {
|
2017-03-04 12:23:09 +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
|
|
|
|
|
) {
|
2017-03-04 12:23:09 +08:00
|
|
|
workInProgress.effectTag |= Update;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-02 08:41:36 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-30 10:09:45 +08:00
|
|
|
const shouldUpdate = checkShouldComponentUpdate(
|
2016-10-27 07:56:46 +08:00
|
|
|
workInProgress,
|
|
|
|
|
oldProps,
|
|
|
|
|
newProps,
|
2016-12-30 10:09:45 +08:00
|
|
|
oldState,
|
2016-11-12 05:14:20 +08:00
|
|
|
newState,
|
2017-03-14 08:05:18 +08:00
|
|
|
newContext,
|
2016-12-30 10:09:45 +08:00
|
|
|
);
|
2016-10-19 17:06:36 +08:00
|
|
|
|
2016-12-30 10:09:45 +08:00
|
|
|
if (shouldUpdate) {
|
|
|
|
|
if (typeof instance.componentWillUpdate === 'function') {
|
2017-11-07 00:07:08 +08:00
|
|
|
startPhaseTimer(workInProgress, 'componentWillUpdate');
|
2016-12-30 10:09:45 +08:00
|
|
|
instance.componentWillUpdate(newProps, newState, newContext);
|
2017-11-07 00:07:08 +08:00
|
|
|
stopPhaseTimer();
|
2017-11-18 02:49:54 +08:00
|
|
|
|
|
|
|
|
// Simulate an async bailout/interruption by invoking lifecycle twice.
|
|
|
|
|
if (debugRenderPhaseSideEffects) {
|
|
|
|
|
instance.componentWillUpdate(newProps, newState, newContext);
|
|
|
|
|
}
|
2016-12-30 10:09:45 +08:00
|
|
|
}
|
2017-03-04 12:23:09 +08:00
|
|
|
if (typeof instance.componentDidUpdate === 'function') {
|
|
|
|
|
workInProgress.effectTag |= Update;
|
|
|
|
|
}
|
2016-12-30 10:09:45 +08:00
|
|
|
} else {
|
2017-03-04 12:23:09 +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
|
|
|
|
|
) {
|
2017-03-04 12:23:09 +08:00
|
|
|
workInProgress.effectTag |= Update;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-30 10:09:45 +08:00
|
|
|
|
|
|
|
|
// 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);
|
2016-10-19 17:06:36 +08:00
|
|
|
}
|
|
|
|
|
|
2016-12-30 10:09:45 +08:00
|
|
|
// Update the existing instance's state, props, and context pointers even
|
|
|
|
|
// if shouldComponentUpdate returns false.
|
2016-10-19 17:06:36 +08:00
|
|
|
instance.props = newProps;
|
|
|
|
|
instance.state = newState;
|
2016-11-12 05:14:20 +08:00
|
|
|
instance.context = newContext;
|
2016-12-30 10:09:45 +08:00
|
|
|
|
|
|
|
|
return shouldUpdate;
|
2016-10-12 01:43:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
2016-10-19 17:06:36 +08:00
|
|
|
adoptClassInstance,
|
|
|
|
|
constructClassInstance,
|
|
|
|
|
mountClassInstance,
|
2017-06-20 05:58:30 +08:00
|
|
|
// resumeMountClassInstance,
|
2016-10-19 17:06:36 +08:00
|
|
|
updateClassInstance,
|
2016-10-12 01:43:47 +08:00
|
|
|
};
|
2017-11-03 03:50:03 +08:00
|
|
|
}
|