为什么react的setState是异步的?

前言

大概几个月之前我有幸带几个小伙伴做了几个项目, 一直以来我都知道reactsetState是异步的, 以及如何正确的使用setState, 直到刚才提到的带着几个小伙伴做项目, 其中一个问我说他用setState修改了一个state, 但使用这个state的时候依旧是之前的值, 然后我才发现setState是一个异步的方法这个特性我忘记跟他们说了, 可能是由于我对这个太习以为常了吧, 觉得这个特性对我来说太自然了, 而这也是我打算写这篇文章的初衷

场景

那个使用了setState之后state依旧是旧值的场景大概如下:

import React, { PureComponent } from 'react';

class Demo extends PureComponent {

  state = {
    count: 0
  }

  click = () => {
    const { count } = this.state;
    this.setState({
      count: count + 1
    });
    console.log(this.state.count);

  }

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

    return (
      <div>
        <button onClick={this.click}>+1</button>
        <div>{count}</div>
      </div>
    );
  }
}

export default Demo;
复制代码

由于setState是异步的, 那么此时我们调用setState, 并且输出state, 那么得到的永远会是修改之前的state

而解决方法有以下大致两种

方法一: 回调函数

我们把上面的setState的代码修改为这样:

this.setState(
  {
    count: count + 1
  },
  () => {
    console.log(this.state.count);
  }
);
复制代码

这个时候我们输出的state就是修改之后的值了

方法二: componentDidUpdate

还有一个方法就是使用componentDidUpdate这个生命周期方法, 这个生命周期方法会在组件挂载到DOM树上之后, 组件重新渲染之后执行, 使用这个生命周期方法的话, 那么代码大致如下:

import React, { PureComponent } from 'react';

class Demo extends PureComponent {

  state = {
    count: 0
  }

  componentDidUpdate() {
    const { count } = this.state;
    console.log(count);
  }

  click = () => {
    const { count } = this.state;
    this.setState({
      count: count + 1
    });
  }

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

    return (
      <div>
        <button onClick={this.click}>+1</button>
        <div>{count}</div>
      </div>
    );
  }
}

export default Demo;
复制代码

为他解决了这个问题之后我就开始思考这个问题了: 为何setState是异步的呢?

为何setState是异步的

私以为是基于性能和体验的考虑, 试想一下如果我们需要更新多个state:

import React, { PureComponent } from 'react';
import Comp1 from '@/components/Comp1';
import Comp2 from '@/components/Comp2';
import Comp3 from '@/components/Comp3';

class Demo extends PureComponent {

  state = {
    _1: 1,
    _2: 2,
    _3: 3
  }

  set1 = () => {
    const { _1 } = this.state;
    this.setState({
      _1: _1 + 1
    });
  }

  set2 = () => {
    const { _2 } = this.state;
    this.setState({
      _2: _2 + 1
    });
  }

  set3 = () => {
    const { _3 } = this.state;
    this.setState({
      _3: _3 + 1
    });
  }

  render() {

    return (
      <div>
        <Comp1 click={this.set1} />
        <Comp2 click={this.set2} />
        <Comp3 click={this.set3} />
      </div>
    );
  }
}

export default Demo;
复制代码

此时需要调用setState, 如果是同步的, 那么程序就会发生阻塞, 而react的处理方法是将多个更新聚集(flush)到一起, 然后执行一个批处理(batch), 这样就能避免阻塞的发生

假设此时用户在做一些交互操作, 比如输入的操作, 那么异步的操作不会导致用户的收入行为被阻塞, 而是把更新渲染往后延, 这样体验会更好

同时还有一点, 当然这个是我个人的一个体会, 源于最近刚完成的一个大数据可视化的项目, 是展现一个数据处理流程的可视化大屏的项目, 这样的项目免不了动画和数据计算处理, 由于就一个页面, 不存在路由的切换, 同时数据都是假数据, 因此并未使用redux, 而是直接使用了state, 同时又有很多的用户交互, 而程序要响应用户的交互, 并据此来更新界面, 那么自然就会有很多setState的动作了, 而此时如果setState是同步的, 那么当计算和动画流程运行起来, 用户和程序产生交互程序更新界面的时候, 更新的动作就会使得程序阻塞, 此时动画和数字的计算就会停止, 那就麻烦了

当然了, 这只是个人的一点愚见, 同时也欢迎大家在评论区和我探讨, 同时附上官方文档中关于这个问题的解释和github中关于这个问题的探讨

参考文献

  1. When is setState asynchronous?

  2. RFClarification: why is setState asynchronous?

おすすめ

転載: juejin.im/post/7076751722810933285