/** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @providesModule ReactUpdates */ 'use strict'; var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); var ReactFeatureFlags = require('ReactFeatureFlags'); var ReactReconciler = require('ReactReconciler'); var Transaction = require('Transaction'); var invariant = require('invariant'); var dirtyComponents = []; var updateBatchNumber = 0; var asapCallbackQueue = CallbackQueue.getPooled(); var asapEnqueued = false; var batchingStrategy = null; function ensureInjected() { invariant( ReactUpdates.ReactReconcileTransaction && batchingStrategy, 'ReactUpdates: must inject a reconcile transaction class and batching ' + 'strategy', ); } 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, ); } Object.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; invariant( len === dirtyComponents.length, "Expected flush transaction's stored dirty-components length (%s) to " + 'match dirty-components array length (%s).', 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); // 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) { invariant( batchingStrategy.isBatchingUpdates, "ReactUpdates.asap: Can't enqueue an asap callback in a context where" + 'updates are not being batched.', ); asapCallbackQueue.enqueue(callback, context); asapEnqueued = true; } var ReactUpdatesInjection = { injectReconcileTransaction: function(ReconcileTransaction) { invariant( ReconcileTransaction, 'ReactUpdates: must provide a reconcile transaction class', ); ReactUpdates.ReactReconcileTransaction = ReconcileTransaction; }, injectBatchingStrategy: function(_batchingStrategy) { invariant( _batchingStrategy, 'ReactUpdates: must provide a batching strategy', ); invariant( typeof _batchingStrategy.batchedUpdates === 'function', 'ReactUpdates: must provide a batchedUpdates() function', ); invariant( typeof _batchingStrategy.isBatchingUpdates === 'boolean', 'ReactUpdates: must provide an isBatchingUpdates boolean attribute', ); 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;