250 lines
9.3 KiB
JavaScript
250 lines
9.3 KiB
JavaScript
/**
|
|
* Copyright 2013-present, 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.
|
|
*
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var _prodInvariant = require('./reactProdInvariant'),
|
|
_assign = require('object-assign');
|
|
|
|
var CallbackQueue = require('./CallbackQueue');
|
|
var PooledClass = require('./PooledClass');
|
|
var ReactFeatureFlags = require('./ReactFeatureFlags');
|
|
var ReactReconciler = require('./ReactReconciler');
|
|
var Transaction = require('./Transaction');
|
|
|
|
var invariant = require('fbjs/lib/invariant');
|
|
|
|
var dirtyComponents = [];
|
|
var updateBatchNumber = 0;
|
|
var asapCallbackQueue = CallbackQueue.getPooled();
|
|
var asapEnqueued = false;
|
|
|
|
var batchingStrategy = null;
|
|
|
|
function ensureInjected() {
|
|
!(ReactUpdates.ReactReconcileTransaction && batchingStrategy) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must inject a reconcile transaction class and batching strategy') : _prodInvariant('123') : void 0;
|
|
}
|
|
|
|
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(
|
|
/* useCreateElement */true);
|
|
}
|
|
|
|
_assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
|
|
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.perform.call(this, this.reconcileTransaction.perform, this.reconcileTransaction, method, scope, a);
|
|
}
|
|
});
|
|
|
|
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
|
|
|
|
function batchedUpdates(callback, a, b, c, d, e) {
|
|
ensureInjected();
|
|
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
!(len === dirtyComponents.length) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected flush transaction\'s stored dirty-components length (%s) to match dirty-components array length (%s).', len, dirtyComponents.length) : _prodInvariant('124', len, dirtyComponents.length) : void 0;
|
|
|
|
// 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);
|
|
|
|
// Any updates enqueued while reconciling must be performed after this entire
|
|
// batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
|
|
// C, B could update twice in a single batch if C's render enqueues an update
|
|
// to B (since B would have already updated, we should skip it, and the only
|
|
// way we can know to do so is by checking the batch counter).
|
|
updateBatchNumber++;
|
|
|
|
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;
|
|
|
|
var markerName;
|
|
if (ReactFeatureFlags.logTopLevelRenders) {
|
|
var namedComponent = component;
|
|
// Duck type TopLevelWrapper. This is probably always true.
|
|
if (component._currentElement.type.isReactTopLevelWrapper) {
|
|
namedComponent = component._renderedComponent;
|
|
}
|
|
markerName = 'React update: ' + namedComponent.getName();
|
|
console.time(markerName);
|
|
}
|
|
|
|
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
|
|
|
|
if (markerName) {
|
|
console.timeEnd(markerName);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 setState, forceUpdate, etc.; creation and
|
|
// destruction of top-level components is guarded in ReactMount.)
|
|
|
|
if (!batchingStrategy.isBatchingUpdates) {
|
|
batchingStrategy.batchedUpdates(enqueueUpdate, component);
|
|
return;
|
|
}
|
|
|
|
dirtyComponents.push(component);
|
|
if (component._updateBatchNumber == null) {
|
|
component._updateBatchNumber = updateBatchNumber + 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
!batchingStrategy.isBatchingUpdates ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates.asap: Can\'t enqueue an asap callback in a context whereupdates are not being batched.') : _prodInvariant('125') : void 0;
|
|
asapCallbackQueue.enqueue(callback, context);
|
|
asapEnqueued = true;
|
|
}
|
|
|
|
var ReactUpdatesInjection = {
|
|
injectReconcileTransaction: function (ReconcileTransaction) {
|
|
!ReconcileTransaction ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide a reconcile transaction class') : _prodInvariant('126') : void 0;
|
|
ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
|
|
},
|
|
|
|
injectBatchingStrategy: function (_batchingStrategy) {
|
|
!_batchingStrategy ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide a batching strategy') : _prodInvariant('127') : void 0;
|
|
!(typeof _batchingStrategy.batchedUpdates === 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide a batchedUpdates() function') : _prodInvariant('128') : void 0;
|
|
!(typeof _batchingStrategy.isBatchingUpdates === 'boolean') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide an isBatchingUpdates boolean attribute') : _prodInvariant('129') : void 0;
|
|
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; |