Published on

state 到底是同步还是异步的?

18 之前 命中批量更新 是异步 否则同步,18 之后 异步

理解 React state 原理

更新过程

确定优先级

  1. flushSync中的 setState
  2. 正常执行上下文
  3. setTimeout 、 Promise 中的 setState

更新视图

setState 会对 newStateoldState进行浅比较 如果没有发生变化,则组件不更新

同步还是异步

const [count, setCount] = useState(0)
useEffect(() => {
  setCount(1)
  console.log(count) // 0
  setCount(3)
  console.log(count) // 0
}, [])

console.log(`render: ${count}`)
// render: 0
// render: 3

这说明 setState 是异步的。而且两次 setState 只触发了一次 render

同步代码里面会批量更新 batch update

const [count, setCount] = useState(0)
useEffect(() => {
  setCount(1)
  setTimeout(() => {
    setCount(3)
  })
}, [])

console.log(`render: ${count}`)
// render: 0
// render: 1
// render: 3

在 setTimeout 里,每次 setState 都会重新 render

流程

  1. setState 会调用 dispathAction,创建一个 update 对象放到 fiber 节点的 updateQueue 上,然后调度渲染

  2. react 会先从触发 update 的 fiber 往上找到根 fiber 节点,然后再调用函数进行渲染

  3. 而 setState 是同步还是异步,也就是在这一段控制的。因为直接从 setTimeout 执行的异步代码是没有设置 excutionContext 的,那就会走到 NoContext 的分支,会立刻渲染. 可以使用 unstable_batchedUpdates(这里源码设置了excutionContext),用于在异步环境继续开启批量更新。

  4. 等 setState 全部执行完之后再调用渲染,效果就是批量的 setState 了

  5. 如果在 react18,使用新的 createRootAPI,就算是在 setTimeout中也可以批量执行

总结

  1. setState 的同步异步,但这个不是 setTimeout、Promise 那种异步,只是指 setState 之后是否 state 马上变了,是否马上 render。
  2. React 的渲染流程,包括 render 阶段、commit 阶段,render 阶段是从 vdom 转 fiber,包含 schedule 和 reconcile,commit 阶段是把 fiber 更新到 dom。
  3. setState 会创建 update 对象挂到 fiber 对象上,然后调度重新渲染。
  4. 在 react18 里面,如果用 createRoot 的 api,就不会有这种问题了。
  5. setState 是同步还是异步这个问题等 react18 普及以后就不会再有了,因为所有的 setState 都是异步批量执行了。