From 9811b10aaa80f6b08115f40d3e8840e048eca351 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 25 Jan 2015 02:22:16 -0800 Subject: [PATCH] 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. --- src/addons/ReactRAFBatchingStrategy.js | 4 +- src/browser/ui/ReactMount.js | 43 ++++++++++++++++--- src/core/ReactDefaultBatchingStrategy.js | 6 +-- src/core/ReactUpdates.js | 4 +- .../ReactCompositeComponentState-test.js | 8 ++-- 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/addons/ReactRAFBatchingStrategy.js b/src/addons/ReactRAFBatchingStrategy.js index 1d3df579a7..198faf259e 100644 --- a/src/addons/ReactRAFBatchingStrategy.js +++ b/src/addons/ReactRAFBatchingStrategy.js @@ -31,8 +31,8 @@ var ReactRAFBatchingStrategy = { * Call the provided function in a context within which calls to `setState` * and friends are batched such that components aren't updated unnecessarily. */ - batchedUpdates: function(callback, a, b) { - callback(a, b); + batchedUpdates: function(callback, a, b, c, d) { + callback(a, b, c, d); } }; diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index 5b030c447e..7950ff1019 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -233,23 +233,51 @@ function findDeepestCachedAncestor(targetID) { /** * Mounts this component and inserts it into the DOM. * + * @param {ReactComponent} componentInstance The instance to mount. * @param {string} rootID DOM ID of the root node. * @param {DOMElement} container DOM element to mount into. * @param {ReactReconcileTransaction} transaction * @param {boolean} shouldReuseMarkup If true, do not insert markup */ function mountComponentIntoNode( + componentInstance, rootID, container, transaction, shouldReuseMarkup) { var markup = ReactReconciler.mountComponent( - this, rootID, transaction, emptyObject + componentInstance, rootID, transaction, emptyObject ); - this._isTopLevel = true; + componentInstance._isTopLevel = true; ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup); } +/** + * Batched mount. + * + * @param {ReactComponent} componentInstance The instance to mount. + * @param {string} rootID DOM ID of the root node. + * @param {DOMElement} container DOM element to mount into. + * @param {boolean} shouldReuseMarkup If true, do not insert markup + */ +function batchedMountComponentIntoNode( + componentInstance, + rootID, + container, + shouldReuseMarkup) { + var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); + transaction.perform( + mountComponentIntoNode, + null, + componentInstance, + rootID, + container, + transaction, + shouldReuseMarkup + ); + ReactUpdates.ReactReconcileTransaction.release(transaction); +} + /** * Mounting is the process of initializing a React component by creatings its * representative DOM elements and inserting them into a supplied `container`. @@ -368,16 +396,17 @@ var ReactMount = { container ); - var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); - transaction.perform( - mountComponentIntoNode, + // The initial render is synchronous but any updates that happen during + // rendering, in componentWillMount or componentDidMount, will be batched + // according to the current batching strategy. + + ReactUpdates.batchedUpdates( + batchedMountComponentIntoNode, componentInstance, reactRootID, container, - transaction, shouldReuseMarkup ); - ReactUpdates.ReactReconcileTransaction.release(transaction); if (__DEV__) { // Record the root element in case it later gets transplanted. diff --git a/src/core/ReactDefaultBatchingStrategy.js b/src/core/ReactDefaultBatchingStrategy.js index 37e64a36d8..d996adad1d 100644 --- a/src/core/ReactDefaultBatchingStrategy.js +++ b/src/core/ReactDefaultBatchingStrategy.js @@ -54,16 +54,16 @@ var ReactDefaultBatchingStrategy = { * Call the provided function in a context within which calls to `setState` * and friends are batched such that components aren't updated unnecessarily. */ - batchedUpdates: function(callback, a, b) { + batchedUpdates: function(callback, a, b, c, d) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // The code is written this way to avoid extra allocations if (alreadyBatchingUpdates) { - callback(a, b); + callback(a, b, c, d); } else { - transaction.perform(callback, null, a, b); + transaction.perform(callback, null, a, b, c, d); } } }; diff --git a/src/core/ReactUpdates.js b/src/core/ReactUpdates.js index dc84254f06..3311874a26 100644 --- a/src/core/ReactUpdates.js +++ b/src/core/ReactUpdates.js @@ -104,9 +104,9 @@ assign( PooledClass.addPoolingTo(ReactUpdatesFlushTransaction); -function batchedUpdates(callback, a, b) { +function batchedUpdates(callback, a, b, c, d) { ensureInjected(); - batchingStrategy.batchedUpdates(callback, a, b); + batchingStrategy.batchedUpdates(callback, a, b, c, d); } /** diff --git a/src/core/__tests__/ReactCompositeComponentState-test.js b/src/core/__tests__/ReactCompositeComponentState-test.js index ffa1ac2e8a..50e90efb86 100644 --- a/src/core/__tests__/ReactCompositeComponentState-test.js +++ b/src/core/__tests__/ReactCompositeComponentState-test.js @@ -155,9 +155,9 @@ describe('ReactCompositeComponent-state', function() { [ 'componentDidMount-start', 'orange', null ], // setState-sunrise and setState-orange should be called here, // after the bug in #1740 - // componentDidMount() called setState({color:'yellow'}), currently this - // occurs inline. - // In a future where setState() is async, this test result will change. + // componentDidMount() called setState({color:'yellow'}), which is async. + // The update doesn't happen until the next flush. + [ 'componentDidMount-end', 'orange', 'yellow' ], [ 'shouldComponentUpdate-currentState', 'orange', null ], [ 'shouldComponentUpdate-nextState', 'yellow' ], [ 'componentWillUpdate-currentState', 'orange', null ], @@ -166,8 +166,6 @@ describe('ReactCompositeComponent-state', function() { [ 'componentDidUpdate-currentState', 'yellow', null ], [ 'componentDidUpdate-prevState', 'orange' ], [ 'setState-yellow', 'yellow', null ], - // componentDidMount() finally closes. - [ 'componentDidMount-end', 'yellow', null ], [ 'initial-callback', 'yellow', null ], [ 'componentWillReceiveProps-start', 'yellow', null ], // setState({color:'green'}) only enqueues a pending state.