@samitouri / QOS-React-2 / commits / 9811b10aaa

Batch updates in initial render

Currently, the first setState that happens during initial render will start a new batch. Any subsequent updates will be batched. That means that the first setState is synchronous but any subsequent setStates are asynchronous. This commit makes it so that the batching starts at the root. That way all the setStates that happen within life-cycle methods are asynchronous.

Sebastian Markbage committed Jan 25, 2015 at 02:22 UTC 9811b10aaa80f6b08115f40d3e8840e048eca351
5 files changed +46 -19
src/addons/ReactRAFBatchingStrategy.js
+2 -2
@@ -31,8 +31,8 @@ var ReactRAFBatchingStrategy = {
31 * Call the provided function in a context within which calls to `setState`
32 * and friends are batched such that components aren't updated unnecessarily.
33 */
34 - batchedUpdates: function(callback, a, b) {
35 - callback(a, b);
34 + batchedUpdates: function(callback, a, b, c, d) {
35 + callback(a, b, c, d);
36 }
37 };
38
src/browser/ui/ReactMount.js
+36 -7
@@ -233,23 +233,51 @@ function findDeepestCachedAncestor(targetID) {
233 /**
234 * Mounts this component and inserts it into the DOM.
235 *
236 + * @param {ReactComponent} componentInstance The instance to mount.
237 * @param {string} rootID DOM ID of the root node.
238 * @param {DOMElement} container DOM element to mount into.
239 * @param {ReactReconcileTransaction} transaction
240 * @param {boolean} shouldReuseMarkup If true, do not insert markup
241 */
242 function mountComponentIntoNode(
243 + componentInstance,
244 rootID,
245 container,
246 transaction,
247 shouldReuseMarkup) {
248 var markup = ReactReconciler.mountComponent(
247 - this, rootID, transaction, emptyObject
249 + componentInstance, rootID, transaction, emptyObject
250 );
249 - this._isTopLevel = true;
251 + componentInstance._isTopLevel = true;
252 ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup);
253 }
254
255 +/**
256 + * Batched mount.
257 + *
258 + * @param {ReactComponent} componentInstance The instance to mount.
259 + * @param {string} rootID DOM ID of the root node.
260 + * @param {DOMElement} container DOM element to mount into.
261 + * @param {boolean} shouldReuseMarkup If true, do not insert markup
262 + */
263 +function batchedMountComponentIntoNode(
264 + componentInstance,
265 + rootID,
266 + container,
267 + shouldReuseMarkup) {
268 + var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
269 + transaction.perform(
270 + mountComponentIntoNode,
271 + null,
272 + componentInstance,
273 + rootID,
274 + container,
275 + transaction,
276 + shouldReuseMarkup
277 + );
278 + ReactUpdates.ReactReconcileTransaction.release(transaction);
279 +}
280 +
281 /**
282 * Mounting is the process of initializing a React component by creatings its
283 * representative DOM elements and inserting them into a supplied `container`.
@@ -368,16 +396,17 @@ var ReactMount = {
396 container
397 );
398
371 - var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
372 - transaction.perform(
373 - mountComponentIntoNode,
399 + // The initial render is synchronous but any updates that happen during
400 + // rendering, in componentWillMount or componentDidMount, will be batched
401 + // according to the current batching strategy.
402 +
403 + ReactUpdates.batchedUpdates(
404 + batchedMountComponentIntoNode,
405 componentInstance,
406 reactRootID,
407 container,
377 - transaction,
408 shouldReuseMarkup
409 );
380 - ReactUpdates.ReactReconcileTransaction.release(transaction);
410
411 if (__DEV__) {
412 // Record the root element in case it later gets transplanted.
src/core/ReactDefaultBatchingStrategy.js
+3 -3
@@ -54,16 +54,16 @@ var ReactDefaultBatchingStrategy = {
54 * Call the provided function in a context within which calls to `setState`
55 * and friends are batched such that components aren't updated unnecessarily.
56 */
57 - batchedUpdates: function(callback, a, b) {
57 + batchedUpdates: function(callback, a, b, c, d) {
58 var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
59
60 ReactDefaultBatchingStrategy.isBatchingUpdates = true;
61
62 // The code is written this way to avoid extra allocations
63 if (alreadyBatchingUpdates) {
64 - callback(a, b);
64 + callback(a, b, c, d);
65 } else {
66 - transaction.perform(callback, null, a, b);
66 + transaction.perform(callback, null, a, b, c, d);
67 }
68 }
69 };
src/core/ReactUpdates.js
+2 -2
@@ -104,9 +104,9 @@ assign(
104
105 PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
106
107 -function batchedUpdates(callback, a, b) {
107 +function batchedUpdates(callback, a, b, c, d) {
108 ensureInjected();
109 - batchingStrategy.batchedUpdates(callback, a, b);
109 + batchingStrategy.batchedUpdates(callback, a, b, c, d);
110 }
111
112 /**
src/core/__tests__/ReactCompositeComponentState-test.js
+3 -5
@@ -155,9 +155,9 @@ describe('ReactCompositeComponent-state', function() {
155 [ 'componentDidMount-start', 'orange', null ],
156 // setState-sunrise and setState-orange should be called here,
157 // after the bug in #1740
158 - // componentDidMount() called setState({color:'yellow'}), currently this
159 - // occurs inline.
160 - // In a future where setState() is async, this test result will change.
158 + // componentDidMount() called setState({color:'yellow'}), which is async.
159 + // The update doesn't happen until the next flush.
160 + [ 'componentDidMount-end', 'orange', 'yellow' ],
161 [ 'shouldComponentUpdate-currentState', 'orange', null ],
162 [ 'shouldComponentUpdate-nextState', 'yellow' ],
163 [ 'componentWillUpdate-currentState', 'orange', null ],
@@ -166,8 +166,6 @@ describe('ReactCompositeComponent-state', function() {
166 [ 'componentDidUpdate-currentState', 'yellow', null ],
167 [ 'componentDidUpdate-prevState', 'orange' ],
168 [ 'setState-yellow', 'yellow', null ],
169 - // componentDidMount() finally closes.
170 - [ 'componentDidMount-end', 'yellow', null ],
169 [ 'initial-callback', 'yellow', null ],
170 [ 'componentWillReceiveProps-start', 'yellow', null ],
171 // setState({color:'green'}) only enqueues a pending state.