专栏首页多云转晴React setState 是异步执行还是同步执行?

React setState 是异步执行还是同步执行?

  1. setState 是同步更新还是异步更新?
  2. 多次调用 setState 函数,React 会不会进行合并操作?

首先是第一个问题,答:setState 有时是同步更新的,而有时却是异步更新。

一般情况下,setState 基本是异步更新,例如:

// handleClick 是一个事件函数
// 当点击按钮时,count 就会 +1
handleClick () {
    this.setState({
        count: this.state.count + 1
    });
    // 每次点击,打印的结果总是上一次的值
    console.log(this.state.count);
}

下面的例子是 setState 同步更新的例子:

clickUpdateCount () {
    this.setState({
      count: this.state.count + 1
    });
}
handleClick () {
    setTimeout(() => {
        this.clickUpdateCount();
        // 打印结果与 count 一致
        console.log(this.state.count);
    },0);
}

setState 放在定时器里就会同步更新。放在自定义事件函数里也会同步更新,例如:

constructor () {
    super()
    this.state = {
        count: 0
    }
    this.clickUpdateCount = this.clickUpdateCount.bind(this);
}
clickUpdateCount () {
    this.setState({
        count: this.state.count + 1
    }); // 直接打印,结果与 count 一致
    console.log(this.state.count);
}
componentDidMount(){
    document.addEventListener('click', this.clickUpdateCount, false);
}
componentWillUnmount(){
    document.removeEventListener('click', this.clickUpdateCount, false);
}

如果不使用定时器或者添加自定义函数获得更新后的状态,可以给 setState 函数传入第二个参数,该参数是一个函数,它会在 state 更新完成后调用。

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

更新合并

一般情况下,多次调用 setState 函数 React 会把 state 对象合并到当前的 state。例如:

clickUpdateCount () {
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
}

虽然多次调用了 setState,但是点击按钮时 count 只增加 1。如果不想合并操作,可以把这些操作放在定时器当中,或者自定义事件当中。或者给 setState 的第一个参数传入函数,例如:

clickUpdateCount () {
    // prevState 是更新前的 state,props 是父组件传来的属性
    this.setState(function(prevState, props) {
        return {    // 返回更新后的 state
            count: prevState.count + 1
        }
    });
    this.setState(function(prevState, props) {
        return {
            count: prevState.count + 1
        }
    });
    this.setState(function(prevState, props) {
        return {
            count: prevState.count + 1
        }
    });
}

下图是 setState 调用时的大致流程。

图中如果条件是 true,则组件会异步更新,而如果是 false,则会同步更新。是否处于 batchUpdate,可以用一个例子来解释,比如下面的代码:

class A extends React.Component{
  state = {
    count: 0
  }
  render(){
    return <div></div>
  }
  increment(){
    // 处于 batchUpdate
    // isBatchingUpdates = true
    this.setState({
      count: this.count + 1
    });
    // isBatchingUpdates = false
  }
}

class B extends React.Component{
  state = {
    count: 0
  }
  render(){
    return <div></div>
  }
  increment(){
    // 处于 batchUpdate
    // isBatchingUpdates = true
    setTimeout(() => {
      this.setState({
        count: this.count + 1
      });
    },0);
    // isBatchingUpdates = false
  }
}

isBatchingUpdates 的初始值是 true,当没有定时器时调用 setState 时该值还是 true,就会异步执行,而 setState 用定时器包裹后,定时器回调还没执行 isBatchingUpdates 就变成了 false,setState 就会同步执行。

props 或者 state 变化时,会调用 UpdateComponent 更新 props 和 state,然后调用 render 函数,生成新的虚拟节点(Vnode),然后 patch(oldVnode, newVnode),老的虚拟 DOM 和新的虚拟 DOM 对比更新视图。patch 分为两个阶段:

  • reconciliation 阶段:执行 diff 算法(纯 JS 计算);
  • commit 阶段:将 diff 结果渲染到 DOM 上。

由于 JavaScript 是单线程,且和 DOM 渲染共用一个线程,当组件足够复杂时,组件更新时计算量和渲染量都很大,同时再有 DOM 操作需求(比如动画、拖拽等),这可能会导致页面卡顿。

React 考虑性能优化,就把 patch 分成了两个阶段,在 reconciliation 阶段将任务拆分,拆分成多个子任务(commit 不能拆分,reconciliation 阶段是纯 JS 计算,比较好控制),当 DOM 渲染时就暂停任务,浏览器空闲时再恢复计算。通过 window.requestIdleCallback API 可以让一些任务在浏览器空闲时调用。

关于 React fiber 和 Concurrent API 可以参考这篇文章:深入剖析 React Concurrent

setState 与 useState

setState 与 useState 功能相似,在一般情况下,useState 也会对多次调用更新函数做合并处理,例如:

let [count, setCount] = useState(0);
// 点击后只会相加一次
let handleClick = useCallback(() => {
    setCount(count + 1);
    setCount(count + 1);
},[count]);

如果 useState 更新 state 传入的也是函数时,就不会对数组做合并处理,这与 setState 行为一样。

let [count, setCount] = useState(0);
let handleClick = useCallback(() => {
    // 每次点击按钮会增加 2
    setCount((prevState) => {
        return ++ prevState;
    });
    setCount((prevState) => {
        return ++ prevState;
    });
},[]);

useStatesetState 不同的是:state 是对象时,setState 会自动合并对象,而 useState 不会。例如:

state = {
    count: 0,
    color: 'red'
};
handleClick(){
    this.setState({
        // 只更新了 count
        count: this.state.count + 1
    });
}

render () {
    return (
      <div>
        <h1>count: { this.state.count }</h1>
        <h2>color: { this.state.color }</h2>
        <button onClick={() => this.handleClick()}>Click +1</button>
      </div>
    );
  }

运行时会发现,color 并不会丢失。而如果使用 useState,只更新 count,当点击按钮一次之后 color 就会丢失,例如:

let handleClick = useCallback(() => {
    setData({
      count: data.count + 1
    });
},[data.count]);

如果不让 color 丢失,可以使用这种方式:

setData({
    ...data,    // 把老的对象也传进来
    count: data.count + 1   // 覆盖老的值
});

setState 可以同步更新,比如在外层包裹定时器,传入第二个回调参数可以拿到更新后的数据。但 useState 是行不通的,它是异步更新,要想及时拿到更新后的数据,就需要借助 useEffect

useEffect(() => {
    console.log(data.count);
},[data.count]);

useEffect 在首次渲染完成后执行一次,之后会在 data.count 的值更新后执行回调函数。

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • react中setState是同步还是异步的

    我们都知道,React框架是由数据来驱动视图变化的,基于状态的管理实现对组件的管理,也就是组件当中的state,通过setState方法来修改当前组件的stat...

    OECOM
  • php异步执行

    开发中遇到了群发邮件慢的问题,找到了以下方法,还不错分享下: PHP异步执行的常用方式常见的有以下几种,可以根据各自优缺点进行选择: 1.客户端页面采用AJA...

    wangxl
  • React中的setState是异步的吗?

    在React中更新状态,一般的写法都是this.setState({a:1}),而非Vue那样this.a = 1。并且,如果在如下场景:

    娜姐
  • JavaScript 同步和异步的执行机制问题

    今天做项目的时候遇到了一个问题,当我在请求完后端数据的时候,想去立刻打印出请求出来的信息时,怎么打印都是空的,但是出来这个请求方法之后他又有数值了,于是我很纳闷...

    wsuo
  • python 实现异步执行

    分析一下,下面的例子: 定义了一个装饰器 async 和 A 、B 两个function A 里面sleep 20s , 然后打印 a function...

    py3study
  • node的异步执行性能

    https://github.com/kyrylkov/promise-async-performance

    欲休
  • 如何实现异步执行

    浏览器和服务器之间只一种面向无连接的HTTP协议进行通讯的,面向无连接的程序的特点是客户端请求服务端,服务端根据请求输出相应的程序,不能保持持久连接。

    公众号php_pachong
  • [深度思考]·为什么CNN是同步(并行)而RNN是异步(串行)的呢?

    个人主页--> https://xiaosongshine.github.io/

    小宋是呢
  • React 中setState更新state何时同步何时异步?

    setState的“异步”并不是说内部由异步代码实现,本身的执行过程和代码都是同步的。

    Clearlove

扫码关注云+社区

领取腾讯云代金券