/** * 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;