你不知道的 React 之 setState() 异步更新数据及其解决办法

1,920 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情

1. setState() 的说明

相信大家 React 没少用,但是你知道 React 的 setState() 方法是异步更新数据的吗?下面我们将通过例子来解析 setState() 是如何异步更新数据的,以及使用过程中应该注意些什么。

1.1 更新数据

setState() 是异步更新数据的

众所周知,React 中是通过 setState() 来更新数据的,但是其实 setState() 更新数据是异步的,代码如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  // 默认状态的值
  state = {
    count: 1
  }
  
  handleClick = () => {
    // 异步更新state
    this.setState({
      count: this.state.count + 1
    })
    console.log(this.state.count)// 1
    
  }
  
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

在上述代码中,我们有一个默认值为1,并且提供了一个点击事件的处理程序,当点击一次按钮时,我们就在该默认值上+1,从而使 count 的值变为2,但是由于我们是直接在 this.setState() 方法后面打印 this.state.count,那么我们实际上拿到的值还是1,说明虽然 this.setState() 方法调用结束了,但是状态并没有立即更新,也证明 this.setState() 是异步更新数据的。 演示效果如下:

1.gif

注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()

实际上 this.setState() 是可以多次调用的,但是由于 this.setState() 是异步更新数据的,所以在第一个 this.setState() 调用完以后再调用第二个 this.setState() 结果不会发生改变。代码如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  state = {
    count: 1
  }

  handleClick = () => {
    // 前面的setState()
    this.setState({
      count: this.state.count + 1// 1 + 1
    })
    console.log('第一次调用完的count:', this.state.count)// 1

    // 后面的setState()
    this.setState({
      count: this.state.count + 1
    })
    console.log('第二次调用完的count:', this.state.count)// 1
  }

  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

效果如下:

2.gif

可以调用多次 setState(),但是只会调用一次重新渲染

虽然 this.setState() 调用了2次,但是 render() 只执行一次(初始化时不算,只有点击按钮以后才算),这是为了性能更好,不会因为每次状态的改变都要重新渲染页面。代码如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  state = {
    count: 1
  }

  handleClick = () => {
    // 前面的setState()
    this.setState({
      count: this.state.count + 1// 1 + 1
    })
    console.log('第一次调用完的count:', this.state.count)// 1

    // 后面的setState()
    this.setState({
      count: this.state.count + 1
    })
    console.log('第二次调用完的count:', this.state.count)// 1
  }

  render() {
    console.log('render调用')
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

效果如下:

3.gif

1.2 推荐语法

如果后一次的 this.setState() 想基于第一次 this.setState() 的结果的基础上做操作,该如何实现?方法如下:

  • 推荐:使用 setState((state,props) => {}) 语法
  • 参数 state:表示最新的 state
  • 参数 props:表示最新的 props
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  state = {
    count: 1
  }

  handleClick = () => {
    // 前面的setState()
    // 更新state
    // 注意:这种方法也是异步更新数据
    this.setState((state, props) => {
      return {
        count: state.count + 1// 1 + 1
      }
    })

    // 后面的setState()
    this.setState((state, props) => {
      console.log('第二次调用:', state)
      return {
        count: state.count + 1
      }
    })
    console.log('count:', this.state.count)// 1
  }

  render() {
    console.log('render')
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

效果如下:

1.gif

1.3 setState() 的第二个参数

  • 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
  • 语法:setSate(updater,[callback])
this.setState(
    (state, props) => {},// 参数1
    () => {console.log('这个回调函数会在状态更新后立即执行')}// 参数2
)

实际上 setState() 方法还有第二个参数,它是一个回调函数,这个回调函数会在状态更新后立即执行,所以如果你希望在状态更新后做些什么的话,可以在这个回调函数中去执行,其使用方式具体如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  state = {
    count: 1
  }

  handleClick = () => {
    this.setState(
      // 参数1
      (state, props) => {
        return { count: state.count + 1 }
      },

      // 参数2:在状态更新后且重新渲染后,立即执行
      () => {
        console.log('状态更新完成:', this.state.count)
        console.log(document.getElementById('title').innerText)
        document.title = '更新后的count为:' + this.state.count
      }
    )
    console.log("异步执行完以后的count:"+this.state.count)// 1
  }

  render() {
    return (
      <div>
        <h1 id='title'>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

效果如下:

2.gif