首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React setState 是异步执行还是同步执行?

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

作者头像
多云转晴
发布2020-08-04 10:45:34
2.5K0
发布2020-08-04 10:45:34
举报
文章被收录于专栏:webTowerwebTower
  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 的值更新后执行回调函数。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebTower 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 更新合并
  • setState 与 useState
相关产品与服务
批量计算
批量计算(BatchCompute,Batch)是为有大数据计算业务的企业、科研单位等提供高性价比且易用的计算服务。批量计算 Batch 可以根据用户提供的批处理规模,智能地管理作业和调动其所需的最佳资源。有了 Batch 的帮助,您可以将精力集中在如何分析和处理数据结果上。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档