一文彻底搞懂React的setState是同步还是异步

4,860 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

引言

相信对React熟悉的读者对setState都不会感到陌生,但是通过面试一些小伙伴也好还是与同事讨论也好,发现大部分人都知道setState是异步的,但是如果深入询问什么场景下是异步的,可不可能是同步,什么场景下又是同步的话又都回答不上来,下面就对此问题进行深入分析,希望能够帮助大家彻底搞懂setState机制。

如何使用setState

首先需要明确的是在 React 的使用中,至关重要的是,不要直接去修改 state。例如:this.state.count = 1是无法触发 React 去更新视图的。因为React的机制规定,一个state的更新,首先需要调用 setState 方法,之后再会进行render函数更新视图。

举例:

this.setState({
    count: 1
})

setState 同步 OR 异步

我们首先需要明确,从 API 层面上说,它就是普通的调用执行的函数,自然是同步的,因此,这里所说的同步和异步指的是 API 调用后更新 DOM 是同步还是异步的。

同步和异步主要取决于它被调用的环境

  • 如果 setState 在 React 能够控制的范围被调用,它就是异步的。 例如:合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行 DOM 更新。
  • 如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。 例如:原生事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 setState 被调用后会立即更新 DOM 。

setState 异步

setState 方法其实是 “异步” 的。即立即执行之后,是无法直接获取到最新的 state 中的值,需要经过 React 对 state 的所有改变进行合并处理之后,才会去计算新的虚拟dom,再根据最新的虚拟dom去重新渲染真实dom。

举例分析:

import React, { Component } from 'react'

class App extends Component {
  state = {
    count: 1,
  }

  handleClick = () => {
    this.setState({
      count: this.state.count + 1,
    })
    console.log(this.state.count) // 1
    
    this.setState({
      count: this.state.count + 1,
    })
    console.log(this.state.count) // 1
  }
  render() {
    return (
      <>
        <button onClick={this.handleClick}>加1</button>
        <div>{this.state.count}</div>
      </>
    )
  }
}

export default App

点击按钮触发事件,打印的都是 1,页面显示 count 的值为 2

在异步中:

  • 对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行结果
  • 如果是同时 setState 多个不同的值,在更新时会对其进行合并后进行批量更新。

setState被设计为异步主要便是为了减少性能开销,从而达到性能优化的目地。

性能优化:

假如 每次 setState 都会触发更新流程 的话,那么每一次 setState的调用都会触发一次 re-render,而re-render 这个过程会涉及到对 DOM 的操作,因此会带来较大的性能开销。

因此,setState 异步的一个重要动机就是避免频繁的 re-render,从而达到性能优化的目地。

setState 同步

setState()实际上可以接受一个函数作为参数,函数的首个参数就是上一次的state,以达到 “同步”的目地。

举例分析:

import React, { Component } from 'react'

class App extends Component {
  state = {
    count: 1,
  }

  handleClick = () => {
    this.setState(prevState => {
      return {count: prevState.count + 1};
    });
    console.log(this.state.count) // 2
    
    this.setState(prevState => {
      return {count: prevState.count + 1};
    });
    console.log(this.state.count) // 3
  }
  render() {
    return (
      <>
        <button onClick={this.handleClick}>加1</button>
        <div>{this.state.count}</div>
      </>
    )
  }
}

export default App

点击按钮触发事件,打印分别是2和3,页面显示 count 的值为 3

总结

  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout中都是同步的。

  • setState 的批量更新优化是建立在“异步”之上的,在“异步”中如果对同一个值进行多次 setState setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新,以达到性能优化的目的。

  • 正常调用setState()的话会形成 “异步”化,但是可以通过setState()接受一个函数作为参数,函数的首个参数就是上一次的state,以达到 “同步”化。

结语

本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力。