如何理解React类式组件中的setState?

在 React 类式组件中,我们并不能直接通过修改 state 的值来让页面发生更新,而是必须通过 setState 的方式让 React 重新渲染页面,setState 来自于哪里呢?来自于继承 React 的 Component 类中,例如定义一个 App 类: class App extends Component ,在类中我们可以直接通过 this.setState 的方式来修改 state 的值,并让其调用 render 函数重新渲染页面。

setState 的三种写法

基本方式

js 复制代码
import React, { Component } from 'react'

export class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      title: "React"
    }
  }
  
  changeText() {
    // 方式一:最基本的使用方式
    this.setState({
      title: "React类组件"
    })
  }
  
  render() {
    const { title } = this.state

    return (
      <div>
        <h1>title: {title}</h1>
        <button onClick={e => this.changeText()}>修改标题</button>
      </div>
    )
  }
}

setState 可以传入一个回调函数

作用:

  1. 可以在回调函数中编写新的 state 的逻辑。
  2. 当前的回调函数会将之前的 stateprops 传递进来。
js 复制代码
import React, { Component } from 'react'

export class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      title: "React"
    }
  }
  
  changeText() {
    // 方式二:在setState中传入回调函数
    this.setState((state, props) => {
      // 可以获取之前的 state 和 props 值
      console.log(state.title, props)
      // 可以编写一些新的对state处理的逻辑
      return {
        title: state.title + "Native"
      }
    }
  }
  
  render() {
    const { title } = this.state
    
    return (
      <div>
        <h1>title: {title}</h1>
        <button onClick={e => this.changeText()}>修改标题</button>
      </div>
    )
  }
}

setState 在 React 的事件处理中是一个异步调用

我们并不能在执行完 setState 之后立马拿到最新的 state 的结果,可以在 setState 中传入第二个参数: callback 回调函数,用来获取数据更新之后(数据合并)的最新值。

js 复制代码
import React, { Component } from 'react'

export class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      title: "React"
    }
  }
  
  changeText() {
    // 方式三:通过 setState 方法的第二个参数, 通过回调函数拿到更新后的值
    this.setState({ title: 'Redux' }, () => {
      console.log("在回调函数中获取更新后的值:", this.state.title) // Redux
    })
    console.log('因为异步获取的还是原来的值', this.state.title) // React
  }
  
  render() {
    const { title } = this.state
    
    return (
      <div>
        <h1>title: {title}</h1>
        <button onClick={e => this.changeText()}>修改标题</button>
      </div>
    )
  }
}

setState 的设计是异步的

React 中 setState 的设计为什么是异步的,针对这一讨论,Redux 的作者认为:setState 设计成异步,一方面可以显著提升性能 ,这是因为 React 每次调用 setState 都会进行一次更新,render 函数就会被频繁的调用,页面频繁的被渲染,为了解决这一问题,批量的进行更新 是最得体的方法;另一方面可以使 stateprops 保持同步,因为存在一种情况,如果同步更新 state,这时还没有执行 render 函数,stateprops 不能保持同步。

js 复制代码
export class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0
    }
  }

  increment() {
    this.setState({
      count: this.state.count + 1
    })
    this.setState({
      count: this.state.count + 1
    })
    this.setState({
      count: this.state.count + 1
    })
    console.log(this.state.count) // 1
  }

  render() {
    const { count } = this.state

    return (
      <div>
        <h1>{count}</h1>
        <button onClick={e => this.increment()}>count+1</button>
      </div>
    )
  }
}

点击增加按钮后 count 值变为 1,因为 setState 默认是一个异步的方法,默认会收集一段时间内所有的更新, 然后再统一更新 React。出于性能考虑,React 会把多个 setState() 调用合并成一个调用,所以点击按钮之后 count值 为 1,再次点击其结果会加 1 变为 2。

其原理 就是通过 Object.assign() 方法对旧 state 和更改后的 state 进行一个合并。

要解决合并这个问题,可以让 setState() 接收一个函数而不是一个对象。

js 复制代码
export class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0
    }
  }

  increment() {
    this.setState((state) => {
      return {
        count: state.count + 1
      }
    })
    this.setState((state) => {
      return {
        count: state.count + 1
      }
    })
    this.setState((state) => {
      return {
        count: state.count + 1
      }
    })
    console.log(this.state.count) // 3
  }

  render() {
    const { count } = this.state

    return (
      <div>
        <h1>{count}</h1>
        <button onClick={e => this.increment()}>count+1</button>
      </div>
    )
  }
}

点击增加按钮后 count 值变为 3,再次点击其结果会在基础上加 3 变为 6。

setState 到底是同步的还是异步的

React18 版本之前 ,在组件生命周期或 React 合成事件中,setState异步的 ;在 setTimeout 或者原生 dom 事件中,setState同步的

js 复制代码
import React, { Component } from 'react'

export class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      title: "React"
    }
  }
  
  changeText() {
    setTimeout(() => {
      // 在react18之前, setTimeout中setState操作, 是同步操作
      this.setState({
        title: 'React学习笔记'
      })
      console.log(this.state.title) // ReactReact学习笔记
    }, 0)
  }
  
  render() {
    const { title } = this.state
    
    return (
      <div>
        <h1>title: {title}</h1>
        <button onClick={e => this.changeText()}>修改标题</button>
      </div>
    )
  }
}

React18版本之后,所有操作默认都是异步处理的

js 复制代码
import React, { Component } from 'react'

export class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      title: "React"
    }
  }
  
  changeText() {
    setTimeout(() => {
      // 在react18之后, setTimeout中setState异步操作(批处理)
      this.setState({
        title: 'React学习笔记'
      })
      console.log(this.state.title) // React
    }, 0)
  }
  
  render() {
    const { title } = this.state
    
    return (
      <div>
        <h1>title: {title}</h1>
        <button onClick={e => this.changeText()}>修改标题</button>
      </div>
    )
  }
}

但是官方表示可以通过 flushSync 函数获取到同步的结果。

js 复制代码
import React, { Component } from 'react'
import { flushSync } from 'react-dom'

export class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      title: "React"
    }
  }
  
  changeText() {
    setTimeout(() => {
      // 执行flushSync函数就可以拿到同步结果
      flushSync(() => {
        this.setState({ title: 'React学习笔记' })
      })
      console.log(this.state.title) // React学习笔记
    }, 0)
  }
  
  render() {
    const { title } = this.state
    
    return (
      <div>
        <h1>title: {title}</h1>
        <button onClick={e => this.changeText()}>修改标题</button>
      </div>
    )
  }
}

由此我们可以总结出 React 中的 setState 还是有很多奥秘的,其背后的设计思想是值得我们学习的,值的强调的是,React18 已经全面拥抱函数式组件,React Hooks 已经脱颖而出。

相关推荐
范文杰2 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪2 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪2 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy3 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom3 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom3 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom3 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom3 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom4 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI4 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端