既然setState是异步的,那为什么源码里面找不到settimeout方法呢?

看了下setState的实现,里面没用到setTimeout,他不是异步的吗?

阅读 795
评论 3月25日提问
    4 个回答

    我们常说 setState 是异步的,指的是通过改方法修改 state 的过程是异步的,即当 setState 执行完以后,state 并不一定已经发生改变;而不是说 setState 本身真的是一个异步方法。

    既然你已经看到源码了,有没有看到 enqueueSetStateenqueueCallback

    评论 赞赏 3月25日

      setState 这个异步不是指 settimeout 那种绝对的异步,而是指 enqueueSetState 方法把待更新的 state 推入任务队列后可能是异步更新的。进入队列后,ReactUpdates 会负责执行队列里的更新任务,而且任务的执行不是顺序的,而是需要考虑任务合并、优先度等等要素,这就导致 state 的更新相当复杂。准确地讲 state 的更新不一定是异步的,只是“不保证同步”。

      评论 赞赏 3月26日

        并不是用了setTimeout才叫异步 o(╯□╰)o

        评论 赞赏 3月26日
          madRain
          • 1.7k

          曾经我也有和题主一样的疑惑,我的疑惑来自于下面的例子:

          handleClick(){
              console.log(this.state.counter);  // 0
              this.setState({counter: 1});
              console.log(this.state.counter);  // 0  
              // ------ 看样子是异步的 ------
              
              setTimeout(() => {
                  console.log(this.state.counter);  // 1
                  this.setState({counter: 2});
                  console.log(this.state.counter);  // 2 
                  // ------- 看起来又像是同步的 ---------
              }, 0);
          }
          render(){
              return <button onClick={this.handleClick.bind(this)}>plus</button>
          }

          为什么会这样呢?因为我并没有看过源码,基于上面的表现,我的猜测是:
          由于 React实现了自己的事件池,一次原生点击事件可能对应了多个onClick调用,如果每setState一次就走一遍 diff……render 流程的话,运算开销就太大了。由于一次原生点击事件对应的所有 onClick 回调都在 React 事件池里, React在处理这些回调里的 setState 的时候并不急于走下一步,而是先把所有更新放到一个队列里存起来,等到事件池的“捕获-对象-冒泡”流程完了之后,再发起一次批量更新,来消化前述队列里的全部更改。因此在批量更新发生之前,访问this.state.counter,得到的都是更改之前的值,这是表现为异步的情形。
          但是对于 setTimeout 的回调函数里的 setStateReact 并无一整套流程来确保在合适的时机发起批量更新,所以 setState 的表现就是同步的了。
          对于在 componentDidMount等生命周期函数里的 setState,其表现应为异步,因为对于 React而言,可以确保搜集到足够的更改之后发起批量更新。


          不过,React 可能会在某一个版本加入异步防抖的功能(也许现在已经有了?),届时以任何方式调用 setState,都是异步的。

          评论 赞赏 3月27日
            撰写回答

            登录后参与交流、获取后续更新提醒