setState为什么有同步异步两种情况?从原理解释。

316 阅读2分钟

为什么react要异步setState?

react的更新粒度是应用级别,而且是脏处理。 ​

每次更新都要将整个应用(fiber树)更新流程走一次。 ​

若每个setState都要直接执行更新应用,性能消耗极大。 ​

于是,有了批处理,让 setState 一批进行处理,减少更新频率。

setState** 异步** 其实应该叫做 **延迟更新 **)

批处理。哪一批?怎么处理?

现在,我们有如下组件。

function FC() {
    const [state, setState] = useState(0);
    const handleClick = () => {
        setState((v) => v + 1)
        setState((v) => v + 1)
        setState((v) => v + 1)
    }
    return (
        <div onClick={handleClick}>{state}</div>
    )
}

思考,若我们是开发者,handleClick 时该如何实现批处理?

  • 收集3个 setState ,构成链表。
  • handleClick 结束后,更新应用,在组件更新时,遍历链表,计算新state,。

逻辑没问题,但是有几个问题。

怎么收集setState? 存储到呢?

解决存储问题。

每个组件对应构造一个节点,称其为 fiber 。 ​

fiber 创建字段 updateQueue 存放 state更新链表。

收集setState。

setState 时,创建一个 udpate 对象,存储着 新state / state更新函数。 ​

然后将其,链接到 updateQueue 的尾部。

什么时机更新应用?

handleClick 结束后,setState就全部收集了,所以在 handleClick 结束后,更新应用。

怎么才能接管 handleClick 函数? ​

接管事件,在 onClick 后执行 handleClick 然后在更新应用。 ​

这样** 批处理,"异步"更新就都实现了。** ​

但是有个 bug,如果我们在 setTimeouAJAXreact 无法接管的异步中 setState ,那就永远无法更新了。 ​

function FC() {
    const [state, setState] = useState(0);
    const handleClick = () => {// 事件执行
        setTimeout(()=>{
            // 1秒后 setState
            setState((v) => v + 1)
            setState((v) => v + 1)
            setState((v) => v + 1)
            // 谁来更新?
        },1000)
     	  // 事件结束 更新应用
    }
    return (
        <div onClick={handleClick}>{state}</div>
    )
}

为了解决此问题,在无法接管的函数中,setState后,立即执行更新应用。即同步更新。

setTimeout(()=>{
  // 1秒后 setState
  setState((v) => v + 1) //更新应用
  setState((v) => v + 1) //更新应用
  setState((v) => v + 1) //更新应用
},1000)

concurrent mode 重构为 优先级+调度更新应用,解决此问题,即自动批处理。)

总结

原理一句话简述:收集state,延迟更新应用。 在无法接管函数时,每次setState都会更新应用。 ​

可以理解为,react能掌控的,则是"异步"(延迟更新),无法掌控,则是同步更新。