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.
295 lines
9.7 KiB
295 lines
9.7 KiB
/** |
|
* 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;
|
|
|