1. setState对状态的改变,可能是”异步”的,也可能是同步的 —— 16.xxx版本,17.xxx已经全部真正异步
  2. 那什么情况下异步的,什么情况下是同步的呢?
  • 看下面的总结吧,后来更新了
  1. setState第二个参数可以填写一个回调函数,可以在这里获取最新的状态(它运行的时机是:当所有状态更新完成——因为有时是多个更新函数统一打包进异步队列嘛,并且重新渲染完成后!
  2. setState第一个参数可以写一个回调函数(异步执行),第一个参数是当前的状态(它是可以信任的!!比如:多个setState修改同一个数据,它就会形成一个异步队列,会保证顺序改变状态),该函数的返回结果会混合(覆盖)掉之前的状态。
  3. 如果某个事件中,需要同步调用多次,需要使用函数的方式得到最新的状态

最佳实践

  1. 把所有的setState当作是异步的
  2. 永远不要信任setState调用之后的状态
  3. 如果需要使用改变之后的状态,需要使用回调函数(setState的第二个参数)
  4. 如果新的状态需要根据之前的状态进行运算,则使用函数的方式改变状态(setState的第一个参数)
  5. React会对异步的setState进行优化(不会对同步的setState进行优化),将多次setState进行合并(将多次状态改变完成后,再统一对state进行改变,然后再触发render)

总结

16.xxx版本

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数(包括componentDidMount)的调用顺序在更新(我理解这应该是指页面真实渲染)之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

17.xxx版本

  1. React的setState是异步的,因为React的渲染机制中使用queueMicrotask或者MessageChannel将更新任务添加进微任务队列或者宏任务队列中以此实现异步渲染。
  2. 为什么要使用异步渲染?在React中会将连续调用的setState进行批量更新(合并更新内容),这样做的目的,是为了避免短时间内连续调用造成不必要的渲染,增加性能的开销。
  3. 批量更新只会在一个微任务或宏任务中进行。

之前React 16.xx版本时,在原生事件和 setTimeout 中都是同步的,那时候React的内部实现并不是真正的异步,而现在17.xx版本内部实现都是使用了异步的方式,所以现在不会再出现在不同的地方调用setState,有的地方出现同步,有的地方出现异步的这种情况。