setState 同步异步 半自动批处理 React18全自动批处理

https://zhuanlan.zhihu.com/p/39512941

setState 的同步和异步

1.setState 只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。注意看下方的代码

2.setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

 函数组件中
  function countAdd(){
    // console.log(count); //0
    // setCount(count+ 1);
    // console.log(count); // 0; '异步'
    setTimeout(() => {
      console.log(count); //0
      setCount(count+ 1);
      console.log(count); // 0; '异步'
    }, 1);
  }
  return <div style={
  {fontSize: '10rem'}} onClick={countAdd}>
    count Add {count}
  </div>
  
  类组件中
  
  class Test2 extends Component{
  constructor() {
    super();
    this.state = {
      count: 0,
    }
  }
  countAdd = () => {
    // console.log(this.state.count); // 0
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // console.log(this.state.count); // 0 '异步'

    setTimeout(() => {
      console.log(this.state.count); // 0
      this.setState({
        count: this.state.count + 1,
      });
      console.log(this.state.count); // 1 '同步'
    }, 1);
  };

  componentDidMount() {
    document.getElementById('testDom').addEventListener('click', ()=>{
      console.log(this.state.count); // 0
      this.setState({
        count: this.state.count + 1,
      });
      console.log(this.state.count); // 1 '同步'
    }, false);
  }

  render() {
    const {count} = this.state;
    return <div style={
  {fontSize: '10rem'}} id={'testDom'}>
      count Add {count}
    </div>;
  }
}
  
  

3.setState 的批量更新优化也是建立在(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。(V18之前的半自动批处理)

React18新特性:Automatic batching 自动批处理

React18新特性:Automatic batching 魔术师卡颂

  1. 什么是批处理(batchedUpdates):React会尝试将同一上下文中触发的更新合并为一个更新

批处理的好处

  1. 合并不必要的更新,减少更新流程调用次数
  2. 状态按顺序保存下来,更新时不会出现「竞争问题」
  3. 最终触发的更新是异步流程,减少浏览器掉帧可能性

批处理的几种类型

  1. v18的「批处理」是自动的
  2. v18之前的 React 使用半自动的「批处理」.
  3. React同时提供了一个API 手动 「批处理」unstable_batchedupdates .
半自动批处理
  1. 在v18之前,只有事件回调、生命周期回调中的更新会批处理,比如上例中的onClick。而在promise、setTimeout等异步回调中不会批处理。
 batchedUpdates(onClick, e);
 batchedUpdates方法是同步调用的。
 如果异步fn的话 在真正执行setState时 batchedUpdates 早已执行完,
 executionContext中已经不包含BatchedContext。 此时触发的更新不会走批处理逻辑。

「只对同步流程中的this.setState进行批处理」,只能说是「半自动」。

手动批处理
onClick() {
  setTimeout(() => {
    ReactDOM.unstable_batchedUpdates(() => {
      this.setState({a: 3});
      this.setState({a: 4});
    })
  })
}

那么两次this.setState调用时上下文中全局变量executionContext中会包含BatchedContext。

v18的自动批处理如何实现的?
  1. 增加调度的流程
  2. 不以全局变量executionContext为批处理依据,而是以更新的「优先级」为依据
什么是优先级?

调用this.setState后源码内部会依次执行:

  1. 根据当前环境选择一个「优先级」lane
  2. 创造一个代表本次更新的update对象,赋予他步骤1的优先级
  3. 将update挂载在当前组件对应fiber(虚拟DOM)上
  4. 进入调度流程
 每次调用this.setState会产生update对象,根据调用的场景他会拥有不同的lane(优先级)
调度流程

在组件对应fiber挂载update后,就会进入「调度流程」。
试想,一个大型应用,在某一时刻,应用的不同组件都触发了更新。
那么在不同组件对应的fiber中会存在不同优先级的update。选出这些update中优先级最高的那个,以该优先级进入更新流程。
整个过程

  1. 获取当前所有优先级中最高的优先级
  2. 将步骤1的优先级作为本次调度的优先级
  3. 看是否已经存在一个调度
  4. 如果已经存在调度,且和当前要调度的优先级一致,则return
  5. 不一致的话就进入调度流程
自动批处理流程
onClick() {
  this.setState({a: 3});
  this.setState({a: 4});
  this.setState({a: 5});
  this.setState({a: 6});
}

只有第一次调用会执行调度,后面几次执行由于优先级和第一次一致会return。

当一定时间过后,第一次调度的回调函数performConcurrentWorkOnRoot会执行,进入更新流程。

由于每次执行this.setState都会创建update并挂载在fiber上。

所以即使只执行一次更新流程,还是能将状态更新到最新。

这就是以「优先级」为依据的「自动批处理」逻辑。

    原文作者:zhongshizhi91
    原文地址: https://blog.csdn.net/shuxiaoxii/article/details/118078934
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞