关于useState中的setter函数更新问题

React官网介绍useState的setter函数时这样说的:

  1. React会等到事件处理函数中的 所有 代码都运行完毕再处理 state 更新
  2. React 会将setter的更新函数加入队列,以便在事件处理函数中的所有其他代码运行后进行处理。
  3. 事件处理函数执行完成后,React 将触发重新渲染。在重新渲染期间,React 将处理队列。更新函数会在渲染期间执行

测试伪代码如下:

function handleClick() {
  console.log('start')

  setCount(count => {
    console.log('click');
    return count + 1;
  })

  console.log('end')
}

按照官方理解:触发handleClick时,打印start,然后把setCount里的回调函数放入队列(并不执行),打印end,事件处理函数(handleClick)代码都运行完毕了,再执行队列里的函数,
所以理论打印顺序为start,end,click
可实际打印顺序是:start,click,end

官网也有例子,就是把setCount里的函数都放在队列里,并不单单是只放return的值,所以说log语句也放入队列了。
为什么打印顺序和理解的不一样?

  • 以下回答由chatgpt基于相关博客总结生成:

    回答:

    针对问题中提到的setState问题,可以简单地总结如下:

    1. setState是异步的,但是React在一些情况下(比如在React事件处理函数中)会启用批处理机制对setState进行优化,从而实现同步更新的效果。

    2. 如果多次在同一个事件处理函数中调用setState,React会将这些调用合并成一次操作,只会执行一次state更新,因此通过多次输出state的值,不能证明setState是同步更新的。

    具体地,我们可以通过以下方式进行验证:

    1. 针对useState的情况,官网中介绍的“React会等到事件处理函数中的所有代码都运行完毕再处理state更新”可以看作是一个理论描述。在实际情况中,由于React支持批处理机制,因此在一些情况下,会实现同步更新的效果。具体来说,React在一些事件处理函数中(如setTimeout、Promise.then等)启用批处理机制,将setState的调用放置到一个队列中,等待当前事件处理函数执行完毕后再进行处理。因此在这些情况下,通过多次输出state的值,可能会发现setState实际上是同步更新的。但是需要注意的是,React并不会对所有事件处理函数都启用批处理机制,因此一般情况下,依然可以认为setState是异步的。

    2. 针对React类组件中的setState,与useState类似,同样存在异步更新的情况。具体来说,如果多次在同一个事件处理函数中调用setState,React会将这些调用合并成一次操作,只会执行一次state更新。因此通过多次输出state的值,不能证明setState是同步更新的。以下代码可以作为一个示例来进行验证:

    import React, { Component } from 'react';
    
    export default class Example extends Component {
      state = {
        val: 0
      };
    
      componentDidMount() {
        this.setState({val: this.state.val + 1}); // 1
        console.log(this.state.val); // 0
        this.setState({val: this.state.val + 1}); // 2
        console.log(this.state.val); // 0
        setTimeout(() => {
          this.setState({val: this.state.val + 1}); // 3
          console.log(this.state.val); // 2
          this.setState({val: this.state.val + 1}); // 4
          console.log(this.state.val); // 3
        }, 0);
      }
    
      render() {
        return null;
      }
    }
    

    在该代码中,通过多次输出state的值,我们可以发现,setState的更新实际上是异步的,而且React会将在同一个事件处理函数中的多次调用进行合并,只会执行一次state更新。同时,在setTimeout中启用了批处理机制,因此setState的更新也会进行同步处理。

    综上所述,React中的setState更新并非一成不变的,它既可以是异步更新,也可以是同步更新,具体取决于所处的环境。因此,在编写代码时,需要注意一些细节,防止因为setState更新的行为不符合预期而出现一些问题。