React setState方法是同步还是异步

setState在React事件处理函数中或React方法中是异步,在setTimeout, Promise等异步方法中或原生事件中是同步。

一些例子

React事件:

class Test extends Component {
  state = { count: 0 };
  handleIncrease = () => {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
  };
  render() {
    console.log("render", this.state.count);
    const { count } = this.state;
    return (
      <div>
        <h2>{count}</h2>
        <button onClick={this.handleIncrease}> +</button>
      </div>
    );
  }
}

如上代码异步更新state,点击button输出0, 0, render 1

setTimeout异步方法中:

class Test extends Component {
  state = { count: 0 };
  handleIncrease = () => {
    setTimeout(() => {
      this.setState({ count: this.state.count + 1 });
      console.log(this.state.count);
      this.setState({ count: this.state.count + 1 });
      console.log(this.state.count);
    });
  };
  render() {
    console.log("render", this.state.count);
    const { count } = this.state;
    return (
      <div>
        <h2>{count}</h2>
        <button onClick={this.handleIncrease}> +</button>
      </div>
    );
  }
}

如上代码同步更新state,点击button输出render 1, 1, render 2, 2

原生事件处理函数中:

class Test extends Component {
  btnRef = React.createRef();
  state = { count: 0 };
  handleIncrease = () => {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
  };
  componentDidMount() {
    this.btnRef.current.addEventListener("click", this.handleIncrease);
  }
  render() {
    console.log("render", this.state.count);
    const { count } = this.state;
    return (
      <div>
        <h2>{count}</h2>
        <button ref={this.btnRef}> +</button>
      </div>
    );
  }
}

如上代码同步更新state,点击button输出render 1, 1, render 2, 2

原理

React中有一个执行上下文变量executionContext,当executionContext === BatchedContext时就异步更新state,当executionContext === NoContext时就同步更新state。

由于在调用React事件回调,React生命周期等React可以检测到的方法的时候,React默认通过batchedUpdates方法做了额外处理:调用方法前设置executionContext = BatchedContext,调用方法后把执行上下文恢复到同步executionContext = NoContext,所以方法中的setState是异步更新。

batchedUpdates方法:

let executionContext = NoContext; //默认值
export function batchedUpdates(fn) {
  let preExecutionContext = executionContext;
  executionContext |= BatchedContext;
  fn();
  executionContext = preExecutionContext;
}

当setState在异步方法中时,同步方法执行完后才会调用调用异步任务,此时最后一条语句executionContext = preExecutionContext;已经执行完成,executionContext === NoContext,这时再执行异步任务中的setState, 就会同步更新。

原生事件中由于没有设置executionContext = BatchedContext,所以是同步更新state,通过batchedUpdates方法可以实现异步更新。

import React, { Component } from "react";
import { unstable_batchedUpdates } from "react-dom";

class Test extends Component {
  btnRef = React.createRef();
  state = { count: 0 };
  handleIncrease = () => {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
  };
  componentDidMount() {
    // this.btnRef.current.addEventListener("click", this.handleIncrease);
    this.btnRef.current.addEventListener("click", () =>
      unstable_batchedUpdates(this.handleIncrease)
    );
  }
  render() {
    console.log("render", this.state.count);
    const { count } = this.state;
    return (
      <div>
        <h2>{count}</h2>
        <button ref={this.btnRef}> +</button>
      </div>
    );
  }
}

export default Test;

Concurrent模式下的setState

Concurrent模式下优化了setState更新方式,默认都会采用异步更新。

总结

  • 并发模式setState不管在哪里默认都会批量更新;
  • 同步模式用了batchUpdates就是批量更新,没用就是同步更新;
  • 同步模式下,事件函数或生命周期函数setState是批量的,是因为使用了batchUpdates。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容