UNPKG

9.33 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _prodInvariant = require('./reactProdInvariant'),
12 _assign = require('object-assign');
13
14var CallbackQueue = require('./CallbackQueue');
15var PooledClass = require('./PooledClass');
16var ReactFeatureFlags = require('./ReactFeatureFlags');
17var ReactReconciler = require('./ReactReconciler');
18var Transaction = require('./Transaction');
19
20var invariant = require('fbjs/lib/invariant');
21
22var dirtyComponents = [];
23var updateBatchNumber = 0;
24var asapCallbackQueue = CallbackQueue.getPooled();
25var asapEnqueued = false;
26
27var batchingStrategy = null;
28
29function ensureInjected() {
30 !(ReactUpdates.ReactReconcileTransaction && batchingStrategy) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must inject a reconcile transaction class and batching strategy') : _prodInvariant('123') : void 0;
31}
32
33var NESTED_UPDATES = {
34 initialize: function () {
35 this.dirtyComponentsLength = dirtyComponents.length;
36 },
37 close: function () {
38 if (this.dirtyComponentsLength !== dirtyComponents.length) {
39 // Additional updates were enqueued by componentDidUpdate handlers or
40 // similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
41 // these new updates so that if A's componentDidUpdate calls setState on
42 // B, B will update before the callback A's updater provided when calling
43 // setState.
44 dirtyComponents.splice(0, this.dirtyComponentsLength);
45 flushBatchedUpdates();
46 } else {
47 dirtyComponents.length = 0;
48 }
49 }
50};
51
52var UPDATE_QUEUEING = {
53 initialize: function () {
54 this.callbackQueue.reset();
55 },
56 close: function () {
57 this.callbackQueue.notifyAll();
58 }
59};
60
61var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
62
63function ReactUpdatesFlushTransaction() {
64 this.reinitializeTransaction();
65 this.dirtyComponentsLength = null;
66 this.callbackQueue = CallbackQueue.getPooled();
67 this.reconcileTransaction = ReactUpdates.ReactReconcileTransaction.getPooled(
68 /* useCreateElement */true);
69}
70
71_assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
72 getTransactionWrappers: function () {
73 return TRANSACTION_WRAPPERS;
74 },
75
76 destructor: function () {
77 this.dirtyComponentsLength = null;
78 CallbackQueue.release(this.callbackQueue);
79 this.callbackQueue = null;
80 ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
81 this.reconcileTransaction = null;
82 },
83
84 perform: function (method, scope, a) {
85 // Essentially calls `this.reconcileTransaction.perform(method, scope, a)`
86 // with this transaction's wrappers around it.
87 return Transaction.perform.call(this, this.reconcileTransaction.perform, this.reconcileTransaction, method, scope, a);
88 }
89});
90
91PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
92
93function batchedUpdates(callback, a, b, c, d, e) {
94 ensureInjected();
95 return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
96}
97
98/**
99 * Array comparator for ReactComponents by mount ordering.
100 *
101 * @param {ReactComponent} c1 first component you're comparing
102 * @param {ReactComponent} c2 second component you're comparing
103 * @return {number} Return value usable by Array.prototype.sort().
104 */
105function mountOrderComparator(c1, c2) {
106 return c1._mountOrder - c2._mountOrder;
107}
108
109function runBatchedUpdates(transaction) {
110 var len = transaction.dirtyComponentsLength;
111 !(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;
112
113 // Since reconciling a component higher in the owner hierarchy usually (not
114 // always -- see shouldComponentUpdate()) will reconcile children, reconcile
115 // them before their children by sorting the array.
116 dirtyComponents.sort(mountOrderComparator);
117
118 // Any updates enqueued while reconciling must be performed after this entire
119 // batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
120 // C, B could update twice in a single batch if C's render enqueues an update
121 // to B (since B would have already updated, we should skip it, and the only
122 // way we can know to do so is by checking the batch counter).
123 updateBatchNumber++;
124
125 for (var i = 0; i < len; i++) {
126 // If a component is unmounted before pending changes apply, it will still
127 // be here, but we assume that it has cleared its _pendingCallbacks and
128 // that performUpdateIfNecessary is a noop.
129 var component = dirtyComponents[i];
130
131 // If performUpdateIfNecessary happens to enqueue any new updates, we
132 // shouldn't execute the callbacks until the next render happens, so
133 // stash the callbacks first
134 var callbacks = component._pendingCallbacks;
135 component._pendingCallbacks = null;
136
137 var markerName;
138 if (ReactFeatureFlags.logTopLevelRenders) {
139 var namedComponent = component;
140 // Duck type TopLevelWrapper. This is probably always true.
141 if (component._currentElement.type.isReactTopLevelWrapper) {
142 namedComponent = component._renderedComponent;
143 }
144 markerName = 'React update: ' + namedComponent.getName();
145 console.time(markerName);
146 }
147
148 ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
149
150 if (markerName) {
151 console.timeEnd(markerName);
152 }
153
154 if (callbacks) {
155 for (var j = 0; j < callbacks.length; j++) {
156 transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
157 }
158 }
159 }
160}
161
162var flushBatchedUpdates = function () {
163 // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
164 // array and perform any updates enqueued by mount-ready handlers (i.e.,
165 // componentDidUpdate) but we need to check here too in order to catch
166 // updates enqueued by setState callbacks and asap calls.
167 while (dirtyComponents.length || asapEnqueued) {
168 if (dirtyComponents.length) {
169 var transaction = ReactUpdatesFlushTransaction.getPooled();
170 transaction.perform(runBatchedUpdates, null, transaction);
171 ReactUpdatesFlushTransaction.release(transaction);
172 }
173
174 if (asapEnqueued) {
175 asapEnqueued = false;
176 var queue = asapCallbackQueue;
177 asapCallbackQueue = CallbackQueue.getPooled();
178 queue.notifyAll();
179 CallbackQueue.release(queue);
180 }
181 }
182};
183
184/**
185 * Mark a component as needing a rerender, adding an optional callback to a
186 * list of functions which will be executed once the rerender occurs.
187 */
188function enqueueUpdate(component) {
189 ensureInjected();
190
191 // Various parts of our code (such as ReactCompositeComponent's
192 // _renderValidatedComponent) assume that calls to render aren't nested;
193 // verify that that's the case. (This is called by each top-level update
194 // function, like setState, forceUpdate, etc.; creation and
195 // destruction of top-level components is guarded in ReactMount.)
196
197 if (!batchingStrategy.isBatchingUpdates) {
198 batchingStrategy.batchedUpdates(enqueueUpdate, component);
199 return;
200 }
201
202 dirtyComponents.push(component);
203 if (component._updateBatchNumber == null) {
204 component._updateBatchNumber = updateBatchNumber + 1;
205 }
206}
207
208/**
209 * Enqueue a callback to be run at the end of the current batching cycle. Throws
210 * if no updates are currently being performed.
211 */
212function asap(callback, context) {
213 invariant(batchingStrategy.isBatchingUpdates, "ReactUpdates.asap: Can't enqueue an asap callback in a context where" + 'updates are not being batched.');
214 asapCallbackQueue.enqueue(callback, context);
215 asapEnqueued = true;
216}
217
218var ReactUpdatesInjection = {
219 injectReconcileTransaction: function (ReconcileTransaction) {
220 !ReconcileTransaction ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide a reconcile transaction class') : _prodInvariant('126') : void 0;
221 ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
222 },
223
224 injectBatchingStrategy: function (_batchingStrategy) {
225 !_batchingStrategy ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide a batching strategy') : _prodInvariant('127') : void 0;
226 !(typeof _batchingStrategy.batchedUpdates === 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide a batchedUpdates() function') : _prodInvariant('128') : void 0;
227 !(typeof _batchingStrategy.isBatchingUpdates === 'boolean') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must provide an isBatchingUpdates boolean attribute') : _prodInvariant('129') : void 0;
228 batchingStrategy = _batchingStrategy;
229 }
230};
231
232var ReactUpdates = {
233 /**
234 * React references `ReactReconcileTransaction` using this property in order
235 * to allow dependency injection.
236 *
237 * @internal
238 */
239 ReactReconcileTransaction: null,
240
241 batchedUpdates: batchedUpdates,
242 enqueueUpdate: enqueueUpdate,
243 flushBatchedUpdates: flushBatchedUpdates,
244 injection: ReactUpdatesInjection,
245 asap: asap
246};
247
248module.exports = ReactUpdates;
\No newline at end of file