You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
9.7 KiB
296 lines
9.7 KiB
10 years ago
|
/**
|
||
|
* Copyright 2015, Facebook, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This source code is licensed under the BSD-style license found in the
|
||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||
|
*
|
||
|
* @providesModule ReactUpdateQueue
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var ReactLifeCycle = require("./ReactLifeCycle");
|
||
|
var ReactCurrentOwner = require("./ReactCurrentOwner");
|
||
|
var ReactElement = require("./ReactElement");
|
||
|
var ReactInstanceMap = require("./ReactInstanceMap");
|
||
|
var ReactUpdates = require("./ReactUpdates");
|
||
|
|
||
|
var assign = require("./Object.assign");
|
||
|
var invariant = require("./invariant");
|
||
|
var warning = require("./warning");
|
||
|
|
||
|
function enqueueUpdate(internalInstance) {
|
||
|
if (internalInstance !== ReactLifeCycle.currentlyMountingInstance) {
|
||
|
// If we're in a componentWillMount handler, don't enqueue a rerender
|
||
|
// because ReactUpdates assumes we're in a browser context (which is
|
||
|
// wrong for server rendering) and we're about to do a render anyway.
|
||
|
// See bug in #1740.
|
||
|
ReactUpdates.enqueueUpdate(internalInstance);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
ReactCurrentOwner.current == null,
|
||
|
'%s(...): Cannot update during an existing state transition ' +
|
||
|
'(such as within `render`). Render methods should be a pure function ' +
|
||
|
'of props and state.',
|
||
|
callerName
|
||
|
) : invariant(ReactCurrentOwner.current == null));
|
||
|
|
||
|
var internalInstance = ReactInstanceMap.get(publicInstance);
|
||
|
if (!internalInstance) {
|
||
|
if ("production" !== process.env.NODE_ENV) {
|
||
|
// Only warn when we have a callerName. Otherwise we should be silent.
|
||
|
// We're probably calling from enqueueCallback. We don't want to warn
|
||
|
// there because we already warned for the corresponding lifecycle method.
|
||
|
("production" !== process.env.NODE_ENV ? warning(
|
||
|
!callerName,
|
||
|
'%s(...): Can only update a mounted or mounting component. ' +
|
||
|
'This usually means you called %s() on an unmounted ' +
|
||
|
'component. This is a no-op.',
|
||
|
callerName,
|
||
|
callerName
|
||
|
) : null);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if (internalInstance === ReactLifeCycle.currentlyUnmountingInstance) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return internalInstance;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ReactUpdateQueue allows for state updates to be scheduled into a later
|
||
|
* reconciliation step.
|
||
|
*/
|
||
|
var ReactUpdateQueue = {
|
||
|
|
||
|
/**
|
||
|
* Enqueue a callback that will be executed after all the pending updates
|
||
|
* have processed.
|
||
|
*
|
||
|
* @param {ReactClass} publicInstance The instance to use as `this` context.
|
||
|
* @param {?function} callback Called after state is updated.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueCallback: function(publicInstance, callback) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
typeof callback === 'function',
|
||
|
'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
|
||
|
'`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
|
||
|
'isn\'t callable.'
|
||
|
) : invariant(typeof callback === 'function'));
|
||
|
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
|
||
|
|
||
|
// Previously we would throw an error if we didn't have an internal
|
||
|
// instance. Since we want to make it a no-op instead, we mirror the same
|
||
|
// behavior we have in other enqueue* methods.
|
||
|
// We also need to ignore callbacks in componentWillMount. See
|
||
|
// enqueueUpdates.
|
||
|
if (!internalInstance ||
|
||
|
internalInstance === ReactLifeCycle.currentlyMountingInstance) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if (internalInstance._pendingCallbacks) {
|
||
|
internalInstance._pendingCallbacks.push(callback);
|
||
|
} else {
|
||
|
internalInstance._pendingCallbacks = [callback];
|
||
|
}
|
||
|
// TODO: The callback here is ignored when setState is called from
|
||
|
// componentWillMount. Either fix it or disallow doing so completely in
|
||
|
// favor of getInitialState. Alternatively, we can disallow
|
||
|
// componentWillMount during server-side rendering.
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
enqueueCallbackInternal: function(internalInstance, callback) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
typeof callback === 'function',
|
||
|
'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
|
||
|
'`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
|
||
|
'isn\'t callable.'
|
||
|
) : invariant(typeof callback === 'function'));
|
||
|
if (internalInstance._pendingCallbacks) {
|
||
|
internalInstance._pendingCallbacks.push(callback);
|
||
|
} else {
|
||
|
internalInstance._pendingCallbacks = [callback];
|
||
|
}
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Forces an update. This should only be invoked when it is known with
|
||
|
* certainty that we are **not** in a DOM transaction.
|
||
|
*
|
||
|
* You may want to call this when you know that some deeper aspect of the
|
||
|
* component's state has changed but `setState` was not called.
|
||
|
*
|
||
|
* This will not invoke `shouldUpdateComponent`, but it will invoke
|
||
|
* `componentWillUpdate` and `componentDidUpdate`.
|
||
|
*
|
||
|
* @param {ReactClass} publicInstance The instance that should rerender.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueForceUpdate: function(publicInstance) {
|
||
|
var internalInstance = getInternalInstanceReadyForUpdate(
|
||
|
publicInstance,
|
||
|
'forceUpdate'
|
||
|
);
|
||
|
|
||
|
if (!internalInstance) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
internalInstance._pendingForceUpdate = true;
|
||
|
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Replaces all of the state. Always use this or `setState` to mutate state.
|
||
|
* You should treat `this.state` as immutable.
|
||
|
*
|
||
|
* There is no guarantee that `this.state` will be immediately updated, so
|
||
|
* accessing `this.state` after calling this method may return the old value.
|
||
|
*
|
||
|
* @param {ReactClass} publicInstance The instance that should rerender.
|
||
|
* @param {object} completeState Next state.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueReplaceState: function(publicInstance, completeState) {
|
||
|
var internalInstance = getInternalInstanceReadyForUpdate(
|
||
|
publicInstance,
|
||
|
'replaceState'
|
||
|
);
|
||
|
|
||
|
if (!internalInstance) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
internalInstance._pendingStateQueue = [completeState];
|
||
|
internalInstance._pendingReplaceState = true;
|
||
|
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets a subset of the state. This only exists because _pendingState is
|
||
|
* internal. This provides a merging strategy that is not available to deep
|
||
|
* properties which is confusing. TODO: Expose pendingState or don't use it
|
||
|
* during the merge.
|
||
|
*
|
||
|
* @param {ReactClass} publicInstance The instance that should rerender.
|
||
|
* @param {object} partialState Next partial state to be merged with state.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueSetState: function(publicInstance, partialState) {
|
||
|
var internalInstance = getInternalInstanceReadyForUpdate(
|
||
|
publicInstance,
|
||
|
'setState'
|
||
|
);
|
||
|
|
||
|
if (!internalInstance) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var queue =
|
||
|
internalInstance._pendingStateQueue ||
|
||
|
(internalInstance._pendingStateQueue = []);
|
||
|
queue.push(partialState);
|
||
|
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets a subset of the props.
|
||
|
*
|
||
|
* @param {ReactClass} publicInstance The instance that should rerender.
|
||
|
* @param {object} partialProps Subset of the next props.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueSetProps: function(publicInstance, partialProps) {
|
||
|
var internalInstance = getInternalInstanceReadyForUpdate(
|
||
|
publicInstance,
|
||
|
'setProps'
|
||
|
);
|
||
|
|
||
|
if (!internalInstance) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
internalInstance._isTopLevel,
|
||
|
'setProps(...): You called `setProps` on a ' +
|
||
|
'component with a parent. This is an anti-pattern since props will ' +
|
||
|
'get reactively updated when rendered. Instead, change the owner\'s ' +
|
||
|
'`render` method to pass the correct value as props to the component ' +
|
||
|
'where it is created.'
|
||
|
) : invariant(internalInstance._isTopLevel));
|
||
|
|
||
|
// Merge with the pending element if it exists, otherwise with existing
|
||
|
// element props.
|
||
|
var element = internalInstance._pendingElement ||
|
||
|
internalInstance._currentElement;
|
||
|
var props = assign({}, element.props, partialProps);
|
||
|
internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
|
||
|
element,
|
||
|
props
|
||
|
);
|
||
|
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Replaces all of the props.
|
||
|
*
|
||
|
* @param {ReactClass} publicInstance The instance that should rerender.
|
||
|
* @param {object} props New props.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueReplaceProps: function(publicInstance, props) {
|
||
|
var internalInstance = getInternalInstanceReadyForUpdate(
|
||
|
publicInstance,
|
||
|
'replaceProps'
|
||
|
);
|
||
|
|
||
|
if (!internalInstance) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
internalInstance._isTopLevel,
|
||
|
'replaceProps(...): You called `replaceProps` on a ' +
|
||
|
'component with a parent. This is an anti-pattern since props will ' +
|
||
|
'get reactively updated when rendered. Instead, change the owner\'s ' +
|
||
|
'`render` method to pass the correct value as props to the component ' +
|
||
|
'where it is created.'
|
||
|
) : invariant(internalInstance._isTopLevel));
|
||
|
|
||
|
// Merge with the pending element if it exists, otherwise with existing
|
||
|
// element props.
|
||
|
var element = internalInstance._pendingElement ||
|
||
|
internalInstance._currentElement;
|
||
|
internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
|
||
|
element,
|
||
|
props
|
||
|
);
|
||
|
|
||
|
enqueueUpdate(internalInstance);
|
||
|
},
|
||
|
|
||
|
enqueueElementInternal: function(internalInstance, newElement) {
|
||
|
internalInstance._pendingElement = newElement;
|
||
|
enqueueUpdate(internalInstance);
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
module.exports = ReactUpdateQueue;
|