「React」问:this.setState到底是同步还是异步的?

123 阅读3分钟

在react管控下的setState是异步的,但在setTimeout下的setState是同步。

class组件的setState工作流:

首先setState的入口函数相当于一个分发器,根据入参的不同,会将其分发的不同功能函数中,然后就会继续调用enqueueSetState 这个方法,这个方法主要做了两件事

  • 将新的 state 放进组件的状态队列里;
  • 用 enqueueUpdate 来处理将要更新的实例对象。

enqueueUpdate中通过batchingStrategy对象中的isBatchingUpdates属性去判断当前是否处于批量处理的阶段,如果不是,那就立即更新组件,否则就把组件塞入dirtyComponents 队列里

我们再来看setState为什么出现同步问题?

这是因为 isBatchingUpdates 这个方法,不仅仅会在 setState 之后才被调用。

  • 在首次渲染组件的时候,也会调用一次 isBatchingUpdates

    这是因为在组件的渲染过程中,会按照顺序调用各个生命周期函数,开发者很有可能在声明周期函数中调用 setState,因此,我们需要通过开启 batch 来确保所有的更新都能够进入 dirtyComponents 里去,进而确保初始渲染流程中所有的 setState 都是生效的。

  • 绑定事件的时候,也会调动isBatchingUpdates

  // 进来先锁上
  isBatchingUpdates = true
  console.log('increment setState前的count', this.state.count)
  this.setState({
    count: this.state.count + 1
  });
  console.log('increment setState后的count', this.state.count)
  // 执行完函数再放开
  isBatchingUpdates = false
  
  //如果放到setTimeout中
  // 进来先锁上
  isBatchingUpdates = true
  setTimeout(() => {
    console.log('reduce setState前的count', this.state.count)
    this.setState({
      count: this.state.count - 1
    });
    console.log('reduce setState后的count', this.state.count)
  },0);
  // 执行完函数再放开
  isBatchingUpdates = false

所以我们会发现isBatchingUpdates对于setTimeout完全没有约束力,因为isBatchingUpdates是同步代码,在setState发生调用的时候,isBatchingUpdates已经被置为false了。

如何在如上异步环境下,继续开启批量更新模式呢?

React-Dom 中提供了批量更新方法 unstable_batchedUpdates,可以去手动批量更新

setTimeout(()=>{
    unstable_batchedUpdates(()=>{
        this.setState({ number:this.state.number + 1 })
        console.log(this.state.number)
        this.setState({ number:this.state.number + 1})
        console.log(this.state.number)
        this.setState({ number:this.state.number + 1 })
        console.log(this.state.number) 
    })
})

那么如何提升更新优先级呢?

React-dom 提供了 flushSync ,flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中。React 设定了很多不同优先级的更新任务。如果一次更新任务在 flushSync 回调函数内部,那么将获得一个较高优先级的更新。

handerClick=()=>{
    setTimeout(()=>{
        this.setState({ number: 1  })
    })
    this.setState({ number: 2  })
    ReactDOM.flushSync(()=>{
        this.setState({ number: 3  })
    })
    this.setState({ number: 4  })
}

// 3 4 1

类组件中的 setState 和函数组件中的 useState 有什么异同?

  • 首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。

不同点

  • 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
  • setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
  • setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。