Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81152 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule ReactUpdates
10
*/
11
12
"use strict";
13
14
var CallbackQueue = require('CallbackQueue');
15
var PooledClass = require('PooledClass');
16
var ReactCurrentOwner = require('ReactCurrentOwner');
17
var ReactPerf = require('ReactPerf');
18
var Transaction = require('Transaction');
19
20
var assign = require('Object.assign');
21
var invariant = require('invariant');
22
var warning = require('warning');
23
24
var dirtyComponents = [];
25
var asapCallbackQueue = CallbackQueue.getPooled();
26
var asapEnqueued = false;
27
28
var batchingStrategy = null;
29
30
function ensureInjected() {
31
invariant(
32
ReactUpdates.ReactReconcileTransaction && batchingStrategy,
33
'ReactUpdates: must inject a reconcile transaction class and batching ' +
34
'strategy'
35
);
36
}
37
38
var NESTED_UPDATES = {
39
initialize: function() {
40
this.dirtyComponentsLength = dirtyComponents.length;
41
},
42
close: function() {
43
if (this.dirtyComponentsLength !== dirtyComponents.length) {
44
// Additional updates were enqueued by componentDidUpdate handlers or
45
// similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
46
// these new updates so that if A's componentDidUpdate calls setState on
47
// B, B will update before the callback A's updater provided when calling
48
// setState.
49
dirtyComponents.splice(0, this.dirtyComponentsLength);
50
flushBatchedUpdates();
51
} else {
52
dirtyComponents.length = 0;
53
}
54
}
55
};
56
57
var UPDATE_QUEUEING = {
58
initialize: function() {
59
this.callbackQueue.reset();
60
},
61
close: function() {
62
this.callbackQueue.notifyAll();
63
}
64
};
65
66
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
67
68
function ReactUpdatesFlushTransaction() {
69
this.reinitializeTransaction();
70
this.dirtyComponentsLength = null;
71
this.callbackQueue = CallbackQueue.getPooled();
72
this.reconcileTransaction =
73
ReactUpdates.ReactReconcileTransaction.getPooled();
74
}
75
76
assign(
77
ReactUpdatesFlushTransaction.prototype,
78
Transaction.Mixin, {
79
getTransactionWrappers: function() {
80
return TRANSACTION_WRAPPERS;
81
},
82
83
destructor: function() {
84
this.dirtyComponentsLength = null;
85
CallbackQueue.release(this.callbackQueue);
86
this.callbackQueue = null;
87
ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
88
this.reconcileTransaction = null;
89
},
90
91
perform: function(method, scope, a) {
92
// Essentially calls `this.reconcileTransaction.perform(method, scope, a)`
93
// with this transaction's wrappers around it.
94
return Transaction.Mixin.perform.call(
95
this,
96
this.reconcileTransaction.perform,
97
this.reconcileTransaction,
98
method,
99
scope,
100
a
101
);
102
}
103
});
104
105
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
106
107
function batchedUpdates(callback, a, b) {
108
ensureInjected();
109
batchingStrategy.batchedUpdates(callback, a, b);
110
}
111
112
/**
113
* Array comparator for ReactComponents by owner depth
114
*
115
* @param {ReactComponent} c1 first component you're comparing
116
* @param {ReactComponent} c2 second component you're comparing
117
* @return {number} Return value usable by Array.prototype.sort().
118
*/
119
function mountDepthComparator(c1, c2) {
120
return c1._mountDepth - c2._mountDepth;
121
}
122
123
function runBatchedUpdates(transaction) {
124
var len = transaction.dirtyComponentsLength;
125
invariant(
126
len === dirtyComponents.length,
127
'Expected flush transaction\'s stored dirty-components length (%s) to ' +
128
'match dirty-components array length (%s).',
129
len,
130
dirtyComponents.length
131
);
132
133
// Since reconciling a component higher in the owner hierarchy usually (not
134
// always -- see shouldComponentUpdate()) will reconcile children, reconcile
135
// them before their children by sorting the array.
136
dirtyComponents.sort(mountDepthComparator);
137
138
for (var i = 0; i < len; i++) {
139
// If a component is unmounted before pending changes apply, ignore them
140
// TODO: Queue unmounts in the same list to avoid this happening at all
141
var component = dirtyComponents[i];
142
if (component.isMounted()) {
143
// If performUpdateIfNecessary happens to enqueue any new updates, we
144
// shouldn't execute the callbacks until the next render happens, so
145
// stash the callbacks first
146
var callbacks = component._pendingCallbacks;
147
component._pendingCallbacks = null;
148
component.performUpdateIfNecessary(transaction.reconcileTransaction);
149
150
if (callbacks) {
151
for (var j = 0; j < callbacks.length; j++) {
152
transaction.callbackQueue.enqueue(
153
callbacks[j],
154
component
155
);
156
}
157
}
158
}
159
}
160
}
161
162
var flushBatchedUpdates = ReactPerf.measure(
163
'ReactUpdates',
164
'flushBatchedUpdates',
165
function() {
166
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
167
// array and perform any updates enqueued by mount-ready handlers (i.e.,
168
// componentDidUpdate) but we need to check here too in order to catch
169
// updates enqueued by setState callbacks and asap calls.
170
while (dirtyComponents.length || asapEnqueued) {
171
if (dirtyComponents.length) {
172
var transaction = ReactUpdatesFlushTransaction.getPooled();
173
transaction.perform(runBatchedUpdates, null, transaction);
174
ReactUpdatesFlushTransaction.release(transaction);
175
}
176
177
if (asapEnqueued) {
178
asapEnqueued = false;
179
var queue = asapCallbackQueue;
180
asapCallbackQueue = CallbackQueue.getPooled();
181
queue.notifyAll();
182
CallbackQueue.release(queue);
183
}
184
}
185
}
186
);
187
188
/**
189
* Mark a component as needing a rerender, adding an optional callback to a
190
* list of functions which will be executed once the rerender occurs.
191
*/
192
function enqueueUpdate(component, callback) {
193
invariant(
194
!callback || typeof callback === "function",
195
'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' +
196
'`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
197
'isn\'t callable.'
198
);
199
ensureInjected();
200
201
// Various parts of our code (such as ReactCompositeComponent's
202
// _renderValidatedComponent) assume that calls to render aren't nested;
203
// verify that that's the case. (This is called by each top-level update
204
// function, like setProps, setState, forceUpdate, etc.; creation and
205
// destruction of top-level components is guarded in ReactMount.)
206
warning(
207
ReactCurrentOwner.current == null,
208
'enqueueUpdate(): Render methods should be a pure function of props ' +
209
'and state; triggering nested component updates from render is not ' +
210
'allowed. If necessary, trigger nested updates in ' +
211
'componentDidUpdate.'
212
);
213
214
if (!batchingStrategy.isBatchingUpdates) {
215
batchingStrategy.batchedUpdates(enqueueUpdate, component, callback);
216
return;
217
}
218
219
dirtyComponents.push(component);
220
221
if (callback) {
222
if (component._pendingCallbacks) {
223
component._pendingCallbacks.push(callback);
224
} else {
225
component._pendingCallbacks = [callback];
226
}
227
}
228
}
229
230
/**
231
* Enqueue a callback to be run at the end of the current batching cycle. Throws
232
* if no updates are currently being performed.
233
*/
234
function asap(callback, context) {
235
invariant(
236
batchingStrategy.isBatchingUpdates,
237
'ReactUpdates.asap: Can\'t enqueue an asap callback in a context where' +
238
'updates are not being batched.'
239
);
240
asapCallbackQueue.enqueue(callback, context);
241
asapEnqueued = true;
242
}
243
244
var ReactUpdatesInjection = {
245
injectReconcileTransaction: function(ReconcileTransaction) {
246
invariant(
247
ReconcileTransaction,
248
'ReactUpdates: must provide a reconcile transaction class'
249
);
250
ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
251
},
252
253
injectBatchingStrategy: function(_batchingStrategy) {
254
invariant(
255
_batchingStrategy,
256
'ReactUpdates: must provide a batching strategy'
257
);
258
invariant(
259
typeof _batchingStrategy.batchedUpdates === 'function',
260
'ReactUpdates: must provide a batchedUpdates() function'
261
);
262
invariant(
263
typeof _batchingStrategy.isBatchingUpdates === 'boolean',
264
'ReactUpdates: must provide an isBatchingUpdates boolean attribute'
265
);
266
batchingStrategy = _batchingStrategy;
267
}
268
};
269
270
var ReactUpdates = {
271
/**
272
* React references `ReactReconcileTransaction` using this property in order
273
* to allow dependency injection.
274
*
275
* @internal
276
*/
277
ReactReconcileTransaction: null,
278
279
batchedUpdates: batchedUpdates,
280
enqueueUpdate: enqueueUpdate,
281
flushBatchedUpdates: flushBatchedUpdates,
282
injection: ReactUpdatesInjection,
283
asap: asap
284
};
285
286
module.exports = ReactUpdates;
287
288