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.
278 lines
9.0 KiB
278 lines
9.0 KiB
/** |
|
* Copyright 2013-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 ReactUpdates |
|
*/ |
|
|
|
'use strict'; |
|
|
|
var CallbackQueue = require("./CallbackQueue"); |
|
var PooledClass = require("./PooledClass"); |
|
var ReactCurrentOwner = require("./ReactCurrentOwner"); |
|
var ReactPerf = require("./ReactPerf"); |
|
var ReactReconciler = require("./ReactReconciler"); |
|
var Transaction = require("./Transaction"); |
|
|
|
var assign = require("./Object.assign"); |
|
var invariant = require("./invariant"); |
|
var warning = require("./warning"); |
|
|
|
var dirtyComponents = []; |
|
var asapCallbackQueue = CallbackQueue.getPooled(); |
|
var asapEnqueued = false; |
|
|
|
var batchingStrategy = null; |
|
|
|
function ensureInjected() { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
ReactUpdates.ReactReconcileTransaction && batchingStrategy, |
|
'ReactUpdates: must inject a reconcile transaction class and batching ' + |
|
'strategy' |
|
) : invariant(ReactUpdates.ReactReconcileTransaction && batchingStrategy)); |
|
} |
|
|
|
var NESTED_UPDATES = { |
|
initialize: function() { |
|
this.dirtyComponentsLength = dirtyComponents.length; |
|
}, |
|
close: function() { |
|
if (this.dirtyComponentsLength !== dirtyComponents.length) { |
|
// Additional updates were enqueued by componentDidUpdate handlers or |
|
// similar; before our own UPDATE_QUEUEING wrapper closes, we want to run |
|
// these new updates so that if A's componentDidUpdate calls setState on |
|
// B, B will update before the callback A's updater provided when calling |
|
// setState. |
|
dirtyComponents.splice(0, this.dirtyComponentsLength); |
|
flushBatchedUpdates(); |
|
} else { |
|
dirtyComponents.length = 0; |
|
} |
|
} |
|
}; |
|
|
|
var UPDATE_QUEUEING = { |
|
initialize: function() { |
|
this.callbackQueue.reset(); |
|
}, |
|
close: function() { |
|
this.callbackQueue.notifyAll(); |
|
} |
|
}; |
|
|
|
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING]; |
|
|
|
function ReactUpdatesFlushTransaction() { |
|
this.reinitializeTransaction(); |
|
this.dirtyComponentsLength = null; |
|
this.callbackQueue = CallbackQueue.getPooled(); |
|
this.reconcileTransaction = |
|
ReactUpdates.ReactReconcileTransaction.getPooled(); |
|
} |
|
|
|
assign( |
|
ReactUpdatesFlushTransaction.prototype, |
|
Transaction.Mixin, { |
|
getTransactionWrappers: function() { |
|
return TRANSACTION_WRAPPERS; |
|
}, |
|
|
|
destructor: function() { |
|
this.dirtyComponentsLength = null; |
|
CallbackQueue.release(this.callbackQueue); |
|
this.callbackQueue = null; |
|
ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction); |
|
this.reconcileTransaction = null; |
|
}, |
|
|
|
perform: function(method, scope, a) { |
|
// Essentially calls `this.reconcileTransaction.perform(method, scope, a)` |
|
// with this transaction's wrappers around it. |
|
return Transaction.Mixin.perform.call( |
|
this, |
|
this.reconcileTransaction.perform, |
|
this.reconcileTransaction, |
|
method, |
|
scope, |
|
a |
|
); |
|
} |
|
}); |
|
|
|
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction); |
|
|
|
function batchedUpdates(callback, a, b, c, d) { |
|
ensureInjected(); |
|
batchingStrategy.batchedUpdates(callback, a, b, c, d); |
|
} |
|
|
|
/** |
|
* Array comparator for ReactComponents by mount ordering. |
|
* |
|
* @param {ReactComponent} c1 first component you're comparing |
|
* @param {ReactComponent} c2 second component you're comparing |
|
* @return {number} Return value usable by Array.prototype.sort(). |
|
*/ |
|
function mountOrderComparator(c1, c2) { |
|
return c1._mountOrder - c2._mountOrder; |
|
} |
|
|
|
function runBatchedUpdates(transaction) { |
|
var len = transaction.dirtyComponentsLength; |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
len === dirtyComponents.length, |
|
'Expected flush transaction\'s stored dirty-components length (%s) to ' + |
|
'match dirty-components array length (%s).', |
|
len, |
|
dirtyComponents.length |
|
) : invariant(len === dirtyComponents.length)); |
|
|
|
// Since reconciling a component higher in the owner hierarchy usually (not |
|
// always -- see shouldComponentUpdate()) will reconcile children, reconcile |
|
// them before their children by sorting the array. |
|
dirtyComponents.sort(mountOrderComparator); |
|
|
|
for (var i = 0; i < len; i++) { |
|
// If a component is unmounted before pending changes apply, it will still |
|
// be here, but we assume that it has cleared its _pendingCallbacks and |
|
// that performUpdateIfNecessary is a noop. |
|
var component = dirtyComponents[i]; |
|
|
|
// If performUpdateIfNecessary happens to enqueue any new updates, we |
|
// shouldn't execute the callbacks until the next render happens, so |
|
// stash the callbacks first |
|
var callbacks = component._pendingCallbacks; |
|
component._pendingCallbacks = null; |
|
|
|
ReactReconciler.performUpdateIfNecessary( |
|
component, |
|
transaction.reconcileTransaction |
|
); |
|
|
|
if (callbacks) { |
|
for (var j = 0; j < callbacks.length; j++) { |
|
transaction.callbackQueue.enqueue( |
|
callbacks[j], |
|
component.getPublicInstance() |
|
); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var flushBatchedUpdates = function() { |
|
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents |
|
// array and perform any updates enqueued by mount-ready handlers (i.e., |
|
// componentDidUpdate) but we need to check here too in order to catch |
|
// updates enqueued by setState callbacks and asap calls. |
|
while (dirtyComponents.length || asapEnqueued) { |
|
if (dirtyComponents.length) { |
|
var transaction = ReactUpdatesFlushTransaction.getPooled(); |
|
transaction.perform(runBatchedUpdates, null, transaction); |
|
ReactUpdatesFlushTransaction.release(transaction); |
|
} |
|
|
|
if (asapEnqueued) { |
|
asapEnqueued = false; |
|
var queue = asapCallbackQueue; |
|
asapCallbackQueue = CallbackQueue.getPooled(); |
|
queue.notifyAll(); |
|
CallbackQueue.release(queue); |
|
} |
|
} |
|
}; |
|
flushBatchedUpdates = ReactPerf.measure( |
|
'ReactUpdates', |
|
'flushBatchedUpdates', |
|
flushBatchedUpdates |
|
); |
|
|
|
/** |
|
* Mark a component as needing a rerender, adding an optional callback to a |
|
* list of functions which will be executed once the rerender occurs. |
|
*/ |
|
function enqueueUpdate(component) { |
|
ensureInjected(); |
|
|
|
// Various parts of our code (such as ReactCompositeComponent's |
|
// _renderValidatedComponent) assume that calls to render aren't nested; |
|
// verify that that's the case. (This is called by each top-level update |
|
// function, like setProps, setState, forceUpdate, etc.; creation and |
|
// destruction of top-level components is guarded in ReactMount.) |
|
("production" !== process.env.NODE_ENV ? warning( |
|
ReactCurrentOwner.current == null, |
|
'enqueueUpdate(): Render methods should be a pure function of props ' + |
|
'and state; triggering nested component updates from render is not ' + |
|
'allowed. If necessary, trigger nested updates in ' + |
|
'componentDidUpdate.' |
|
) : null); |
|
|
|
if (!batchingStrategy.isBatchingUpdates) { |
|
batchingStrategy.batchedUpdates(enqueueUpdate, component); |
|
return; |
|
} |
|
|
|
dirtyComponents.push(component); |
|
} |
|
|
|
/** |
|
* Enqueue a callback to be run at the end of the current batching cycle. Throws |
|
* if no updates are currently being performed. |
|
*/ |
|
function asap(callback, context) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
batchingStrategy.isBatchingUpdates, |
|
'ReactUpdates.asap: Can\'t enqueue an asap callback in a context where' + |
|
'updates are not being batched.' |
|
) : invariant(batchingStrategy.isBatchingUpdates)); |
|
asapCallbackQueue.enqueue(callback, context); |
|
asapEnqueued = true; |
|
} |
|
|
|
var ReactUpdatesInjection = { |
|
injectReconcileTransaction: function(ReconcileTransaction) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
ReconcileTransaction, |
|
'ReactUpdates: must provide a reconcile transaction class' |
|
) : invariant(ReconcileTransaction)); |
|
ReactUpdates.ReactReconcileTransaction = ReconcileTransaction; |
|
}, |
|
|
|
injectBatchingStrategy: function(_batchingStrategy) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
_batchingStrategy, |
|
'ReactUpdates: must provide a batching strategy' |
|
) : invariant(_batchingStrategy)); |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
typeof _batchingStrategy.batchedUpdates === 'function', |
|
'ReactUpdates: must provide a batchedUpdates() function' |
|
) : invariant(typeof _batchingStrategy.batchedUpdates === 'function')); |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
typeof _batchingStrategy.isBatchingUpdates === 'boolean', |
|
'ReactUpdates: must provide an isBatchingUpdates boolean attribute' |
|
) : invariant(typeof _batchingStrategy.isBatchingUpdates === 'boolean')); |
|
batchingStrategy = _batchingStrategy; |
|
} |
|
}; |
|
|
|
var ReactUpdates = { |
|
/** |
|
* React references `ReactReconcileTransaction` using this property in order |
|
* to allow dependency injection. |
|
* |
|
* @internal |
|
*/ |
|
ReactReconcileTransaction: null, |
|
|
|
batchedUpdates: batchedUpdates, |
|
enqueueUpdate: enqueueUpdate, |
|
flushBatchedUpdates: flushBatchedUpdates, |
|
injection: ReactUpdatesInjection, |
|
asap: asap |
|
}; |
|
|
|
module.exports = ReactUpdates;
|
|
|