基于版本 react@15.7.0

本质上,看this.setState是否命中batchingStrategy.isBatchingUpdates——组件正在更新。
如果batchingStrategy.isBatchingUpdates = true,则批量合并且更新 this.setState,看起来像异步,实际上不是异步,因为只执行了一次。
如果batchingStrategy.isBatchingUpdates = false,则this.setState是同步的。

设计 isBatchingUpdates 的初衷

我们从一段代码来理解一下。
正如下面的代码,当点击 Child 组件的按钮时,将触发子组件的更新。
由于事件的冒泡机制,父组件也将更新,这将导致子组件再一次的更新。
子组件第一次更新完全是浪费的。所以 React 设计成 setState 不立即触发重新渲染,而是先执行完所有的 事件回调 ,然后用一次重新渲染完成所有更新。

  1. function Parent() {
  2. let [count, setCount] = useState(0);
  3. return (
  4. <div onClick={() => setCount(count + 1)}>
  5. Parent clicked {count} times
  6. <Child />
  7. </div>
  8. );
  9. }
  10. function Child() {
  11. let [count, setCount] = useState(0);
  12. return (
  13. <button onClick={() => setCount(count + 1)}>
  14. Child clicked {count} times
  15. </button>
  16. );
  17. }

示例

  1. import React, { Component } from 'react'
  2. class App extends Component {
  3. state = {
  4. count: 0
  5. }
  6. componentDidMount() {
  7. // 执行完,count = 2
  8. this.btn.onclick = () => {
  9. this.setState({ count: this.state.count + 1 });
  10. this.setState({ count: this.state.count + 1 });
  11. }
  12. }
  13. asyncClick = () => {
  14. // 由于执行完,count = 1,看起来 this.state 像是异步执行的。
  15. // 但过一会之后,count 还是等于 1,这并不符合异步的逻辑啊
  16. this.setState({ count: this.state.count + 1 });
  17. this.setState({ count: this.state.count + 1 });
  18. }
  19. syncClick = () => {
  20. // 执行完,count = 2
  21. setTimeout(() => {
  22. this.setState({ count: this.state.count + 1 });
  23. }, 0)
  24. this.setState({ count: this.state.count + 1 });
  25. }
  26. render() {
  27. return (<div>
  28. <button onClick={this.asyncClick}>异步: 合成事件</button>
  29. <button onClick={this.syncClick}>同步</button>
  30. <button ref={(ref) => { this.btn = ref }}>原生事件</button>
  31. <p>{this.state.count}</p>
  32. </div>)
  33. }
  34. }
  35. export default App

从源码来看

setState 是挂载在 React.Component 类原型上的方法,它会将待处理的 state 存入 enqueueSetState 队列

  1. // React/ReactBaseClasses.js
  2. /**
  3. * @param {object|function} partialState 用于更新当前 state 的 新的部分 state
  4. * @param {?function} callback 当 state 更新完成后,执行的回调函数
  5. *
  6. */
  7. ReactComponent.prototype.setState = function (partialState, callback) {
  8. !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
  9. this.updater.enqueueSetState(this, partialState);
  10. if (callback) {
  11. this.updater.enqueueCallback(this, callback, 'setState');
  12. }
  13. };
  1. // react-dom/ReactUpdateQueue.js
  2. function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
  3. // ReactInstanceMap 是一个映射:组件示例 与 组件示例在 react 内部的特性之间的映射。这不必细究
  4. // ReactInstanceMap:Store a reference from the instance back to the internal representation
  5. var internalInstance = ReactInstanceMap.get(publicInstance);
  6. if (!internalInstance) {
  7. if (process.env.NODE_ENV !== 'production') {
  8. var ctor = publicInstance.constructor;
  9. // Only warn when we have a callerName. Otherwise we should be silent.
  10. // We're probably calling from enqueueCallback. We don't want to warn
  11. // there because we already warned for the corresponding lifecycle method.
  12. process.env.NODE_ENV !== 'production' ? warning(!callerName, '%s(...): Can only update a mounted or mounting component. ' + 'This usually means you called %s() on an unmounted component. ' + 'This is a no-op. Please check the code for the %s component.', callerName, callerName, ctor && (ctor.displayName || ctor.name) || 'ReactClass') : void 0;
  13. }
  14. return null;
  15. }
  16. if (process.env.NODE_ENV !== 'production') {
  17. process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, '%s(...): Cannot update during an existing state transition (such as ' + "within `render` or another component's constructor). Render methods " + 'should be a pure function of props and state; constructor ' + 'side-effects are an anti-pattern, but can be moved to ' + '`componentWillMount`.', callerName) : void 0;
  18. }
  19. return internalInstance;
  20. }
  21. /**
  22. * ReactUpdateQueue allows for state updates to be scheduled into a later
  23. * reconciliation step.
  24. */
  25. const ReactUpdateQueue = {
  26. /**
  27. * @param {ReactClass} publicInstance 需要重新渲染的组件实例.
  28. * @param {object} partialState 待更新的部分 state
  29. * @internal
  30. */
  31. enqueueSetState: function (publicInstance, partialState) {
  32. if (process.env.NODE_ENV !== 'production') {
  33. ReactInstrumentation.debugTool.onSetState();
  34. process.env.NODE_ENV !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : void 0;
  35. }
  36. var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  37. if (!internalInstance) {
  38. return;
  39. }
  40. // 看下面这几行就好,
  41. var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  42. queue.push(partialState);
  43. enqueueUpdate(internalInstance);
  44. },
  45. }
  1. // react-dom/ReactUpdates.js
  2. /**
  3. * Mark a component as needing a rerender, adding an optional callback to a
  4. * list of functions which will be executed once the rerender occurs.
  5. */
  6. function enqueueUpdate(component) {
  7. ensureInjected();
  8. // 命中 isbatchingUpdates,this.setState 批量合并,然后再更新组件
  9. if (!batchingStrategy.isBatchingUpdates) {
  10. batchingStrategy.batchedUpdates(enqueueUpdate, component);
  11. return;
  12. }
  13. dirtyComponents.push(component);
  14. if (component._updateBatchNumber == null) {
  15. component._updateBatchNumber = updateBatchNumber + 1;
  16. }
  17. }
  1. // react-dom/ReactDefaultBatchingStrategy.js
  2. // react 默认的 batchingStrategy,这个就是上面代码 batchingStrategy 默认值
  3. var ReactDefaultBatchingStrategy = {
  4. isBatchingUpdates: false, // 批量更新的标志,true 则批量更新 setState
  5. /**
  6. * Call the provided function in a context within which calls to `setState`
  7. * and friends are batched such that components aren't updated unnecessarily.
  8. */
  9. batchedUpdates: function (callback, a, b, c, d, e) {
  10. var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
  11. ReactDefaultBatchingStrategy.isBatchingUpdates = true;
  12. // The code is written this way to avoid extra allocations
  13. if (alreadyBatchingUpdates) {
  14. return callback(a, b, c, d, e);
  15. } else {
  16. return transaction.perform(callback, null, a, b, c, d, e);
  17. }
  18. }
  19. };

流程图

this.setState 是同步还是异步 - 图1

什么情况命中 isBatchingUpdates

命中

合成事件中的 this.setState

  1. import React, { Component } from 'react'
  2. class App extends Component {
  3. state = {
  4. count: 0
  5. }
  6. asyncClick = () => {
  7. // 由于执行完,count = 1,看起来 this.state 像是异步执行的。
  8. // 但过一会之后,count 还是等于 1,这并不符合异步的逻辑啊
  9. this.setState({ count: this.state.count + 1 });
  10. this.setState({ count: this.state.count + 1 });
  11. }
  12. render() {
  13. return (<div>
  14. <button onClick={this.asyncClick}>异步: 合成事件</button>
  15. <p>{this.state.count}</p>
  16. </div>)
  17. }
  18. }
  19. export default App

生命周期中的 this.setState

  1. import React, { Component } from 'react'
  2. class App extends Component {
  3. state = {
  4. count: 0
  5. }
  6. componentDidMount() {
  7. this.setState({ count: this.state.count + 1 });
  8. this.setState({ count: this.state.count + 1 });
  9. }
  10. render() {
  11. return (<div>
  12. <p>{this.state.count}</p>
  13. </div>)
  14. }
  15. }
  16. export default App

不命中

原生事件中的 this.setState

  1. import React, { Component } from 'react'
  2. class App extends Component {
  3. state = {
  4. count: 0
  5. }
  6. componentDidMount() {
  7. this.btn.onclick = () => {
  8. this.setState({ count: this.state.count + 1 });
  9. this.setState({ count: this.state.count + 1 });
  10. }
  11. }
  12. render() {
  13. return (<div>
  14. <button ref={(ref) => { this.btn = ref }}>同步:原生事件</button>
  15. <p>{this.state.count}</p>
  16. </div>)
  17. }
  18. }
  19. export default App

定时器中的 this.setState

  1. import React, { Component } from 'react'
  2. class App extends Component {
  3. state = {
  4. count: 0
  5. }
  6. syncClick = () => {
  7. // 执行完,count = 2
  8. setTimeout(() => {
  9. this.setState({ count: this.state.count + 1 });
  10. }, 0)
  11. this.setState({ count: this.state.count + 1 });
  12. }
  13. render() {
  14. return (<div>
  15. <button onClick={this.syncClick}>同步</button>
  16. <p>{this.state.count}</p>
  17. </div>)
  18. }
  19. }
  20. export default App

参考资料

《【React深入】setState的执行机制》