React状态-setState

205 阅读5分钟

为什么使用setState

不能直接通过修改state的值来让界面发生更新:

  • 在React中,每个组件都有自己的状态(state)和属性(props),当这些状态或属性发生变化时,React会重新渲染组件并更新UI。为了实现这一过程,React会先生成一个新的虚拟DOM树,然后将其与之前的虚拟DOM树进行比较,以确定需要更新哪些节点。最后,React将这些更新应用到真实的DOM树上,最终实现UI的更新。

  • 如果我们直接修改state的值而不是通过setState等API来更新组件状态,React就无法捕获状态变化,无法生成新的虚拟DOM树,也就无法进行比较和更新UI。这是React不能直接通过修改state来让界面更新的根本原因。

  • 综上所述,React不能直接通过修改state来让界面更新的原因:是因为React采用了一种基于虚拟DOM的更新机制。通过使用setState等API来更新组件状态,可以触发React的更新机制,最终实现UI的更新。

在组件中并没有实现setState的方法,为什么可以调用呢?

  • setState方法是从Component中继承过来的

image.png

*使用

    // 1.setState更多用法
    // 1.基本使用
    this.setState({
      message: "你好啊, 李银河"
    })

    // 2.setState可以传入一个回调函数
    // 好处一: 可以在回调函数中编写新的state的逻辑
    // 好处二: 当前的回调函数会将之前的state和props传递进来
    this.setState((state, props) => {
      // 1.编写一些对新的state处理逻辑
      // 2.可以获取之前的state和props值
      console.log(this.state.message, this.props)

      return {
        message: "你好啊, 李银河"
      }
    })

    // 3.setState在React的事件处理中是一个异步调用
    // 如果希望在数据更新之后(数据合并), 获取到对应的结果执行一些逻辑代码
    // 那么可以在setState中传入第二个参数: callback
    this.setState({ message: "你好啊, 李银河" }, () => {
      console.log("++++++:", this.state.message)
    })

setState异步更新

setState的更新是异步的

  • 可见setState是异步的操作,不能在执行完setState之后立马拿到最新的state的结果
    this.setState({ message: "你好啊, 李银河" }, () => {
      console.log("++++++:", this.state.message)
    })
    // message: "Hello World"
    console.log("------:", this.state.message)

为什么setState设计为异步呢?

  • React核心成员(Redux的作者)Dan Abramov对应的回复: https://github.com/facebook/react/issues/11527#issuecomment-360199710
  • setState设计为异步,可以显著的提升性能;
    • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
    • 最好的办法应该是获取到多个更新,之后进行批量更新;
  • 如果同步更新了state,但是还没有执行render函数,那么renderprops不能保持同步;
    • state和props不能保持一致性,会在开发中产生很多的问题;

如何获取异步的结果

一、setState 的回调

  • setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;
  • 格式如下:setState(partialState, callback)
  this.setState({
    message: "你好"
  },() => {
    console.log(this.state.message)
  })

二、生命周期函数:componentDidUpdate

  • componentDidUpdate是React组件生命周期中的一个方法,用于在组件更新后执行一些操作。当组件的props或state发生变化时,React会调用componentDidUpdate方法,并传递三个参数:prevProps、prevState和snapshot。
  1. prevProps:表示组件更新前的props值,是一个对象类型。可以通过比较当前props和prevProps的差异,来进行一些特定的操作。
  2. prevState:表示组件更新前的state值,是一个对象类型。可以通过比较当前state和prevState的差异,来进行一些特定的操作。
  3. snapshot:是一个可选参数,表示组件更新前的快照(即组件更新前的DOM状态)。通常在getSnapshotBeforeUpdate方法中返回一个快照值,在componentDidUpdate方法中使用快照值来进行一些特定的操作,例如恢复滚动位置等。

在componentDidUpdate方法中,我们可以访问组件的this.state属性,以获取更新后的state值。但需要注意的是,在componentDidUpdate方法中修改state可能会导致组件再次渲染,从而陷入无限循环的问题,因此应该谨慎地使用setState方法。

componentDidUpdate(prevProps,provState,snapshot){
    console.log(this.state.message)
  }

setState一定是异步吗?

setState的两种情况 (React18之前)

  1. 在组件生命周期或React合成事件中,setState是异步;
  2. 在setTimeout或者原生dom事件中,setState是同步;

image.png

image.png

setState默认是异步的(React18之后)

  • 在React18之后,默认所有的操作都被放到了批处理中(异步处理)
  • 希望代码可以同步会拿到,则需要执行特殊的flushSync操作:
 changeText() {
    setTimeout(() => {
      // 在react18之前, setTimeout中setState操作, 是同步操作
      // 在react18之后, setTimeout中setState异步操作(批处理)
      flushSync(() => {
        this.setState({ message: "你好啊, 李银河" })
      })
      console.log(this.state.message)
    }, 0);
  }

面试题:React的setState是同步的还是异步的?React18中是怎么样的?

  • 在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新

  • React的setState是异步的 -- 不要指望在调用 setState 之后,this.state 会立即映射为新的值

  • 在react18之前, 在setTimeout,Promise等中操作setState, 是同步操作

  • 在react18之后, 在setTimeout,Promise等中操作setState,是异步操作(批处理)

    • 如果需要同步的处理怎么办呢? 需要执行特殊的flushSync操作
  • 为什么要将setState设计成异步的

    • 首先,若是将setState设计成同步的,在componentDidMount中请求多个网络请求时,会堵塞后面的网络请求
    componentDidMount() {
      // 网络请求一 : this.setState
      // 网络请求二 : this.setState
      // 网络请求三 : this.setState
      // 如果this.setState设计成同步的,会堵塞后面的网络请求
    }
    
    • 一. setState设计为异步,可以显著的提升性能

      • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
      • 最好的办法应该是获取到多个更新,之后进行批量更新
      // 在一个函数中有多个setState时,
      this.setState({}) --> 先不会更新,而是会加入到队列(queue)中 (先进先出)
      this.setState({}) --> 也加入到队列中
      this.setState({}) --> 也加入到队列中
      // 这里的三个setState会被合并到队列中去
      // 在源码内部是通过do...while从队列中取出依次执行的
      
    • 二: 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步