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.
279 lines
9.0 KiB
279 lines
9.0 KiB
10 years ago
|
/**
|
||
|
* 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;
|