[React 源码] React 18.2 - setState 是同步还是异步?[0.8k 字 - 阅读时长2min]

257 阅读2分钟

legacy 模式 下的 setState

legacy 模式下:也就是 通过 ReactDom.render() 方法来挂载时,setState 在 生命周期函数,事件函数当中是批量异步更新,而在定时器 setTimeout | setInterval, Promsie, addEventListener…… 当中是 同步非批量更新。

如果想要在 legacy 模式下的定时器当中获得批量异步更新,通过使用 React 提供的 batchedUpdates 将更新包裹起来就可以强制获得异步批量更新。而事实上在生命周期,事件函数当中,源码默认包裹了 batchedUpdates 所以生命周期,事件函数当中是异步批量更新。

batheUpdate 原理

export function batchedUpdates<A, R>(fn: A => R, a: A): R {
  const prevExecutionContext = executionContext;
  executionContext |= BatchedContext;
  try {
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    // If there were legacy sync updates, flush them at the end of the outer
    // most batchedUpdates-like method.
    if (
      executionContext === NoContext &&
      // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
      !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
    ) {
      resetRenderTimer();
      flushSyncCallbacksOnlyInLegacyMode();
    }
  }
}

batheUpdate 原理:将模式暂时 noMode 改成了 conCurrent ,然后再去执行点击事件,fiber 节点调度到 scheduleFiberOnRoot 就不会被判断 是 noMode ,所以不会同步执行,而是 通过 performConcurrentWorkOnRoot 批量异步更新。

如果是 setTimeout 没有包裹 batheUpdate 的话,判断是 noMode 就会立即同步执行 setTimeout 中的 setState , 通过 performSyncWorkOnRoot 进行同步更新。

异步批量更新

import * as React from 'react';
import * as ReactDOM from 'react-dom';

class Counter extends React.Component{
  state = {number:0}
  buttonClick = ()=>{
    console.log('buttonClick');
    this.setState({number:this.state.number+1});
    console.log(this.state.number);
    this.setState({number:this.state.number+1});
    console.log(this.state.number);
  }
  divClick = ()=>{
    console.log('divClick');
  }
  render(){
    return (
      <div onClick={this.divClick} id="counter">
        <p>{this.state.number}</p>
        <button onClick={this.buttonClick}>+</button>
      </div>
    )
  }
}
ReactDOM.render(<Counter/>,document.getElementById('root'));

同步非批量

import * as React from 'react';
import * as ReactDOM from 'react-dom';

class Counter extends React.Component{
  state = {number:0}
   setTimeout(()=>{
     this.setState((state)=>({number:state.number+1}),()=>{
       console.log(this.state.number);
     });
    
     this.setState((state)=>({number:state.number+1}),()=>{
       console.log(this.state.number);
     });
    });

  }
  render(){
    return (
      <div onClick={this.divClick} id="counter">
        <p>{this.state.number}</p>
        <button onClick={this.buttonClick}>+</button>
      </div>
    )
  }
}
ReactDOM.render(<Counter/>,document.getElementById('root'));

concurrent 模式 下的 setState

concurrent模式下:也就是通过 ReactDom.creatRoot().render() 方法来挂载时,这里的所有更新 - 生命周期函数,事件函数,定时器 setTimeout | setInterval, Promsie, addEventListener…… 都是异步批量更新。

挂载时,不多说,以异步优先级去调度,异步并发更新

更新时,Hook 更新,不多说,大家可以看看之前的文章。

下面我们来聊聊类组件的更新。

第一:由于组件类 extends Components | PureComponents

PureComponents 本质就是(使用 shalowEquall 重写 shouldUpdate方法)

第二:调用类组件的 setState 方法 ,在setState 方法中调用 this.updater.enqueuSetState 方法。

第三:通过实例的 reactInternals 方法找到 对应的 fiber, createUpdate 创建更新对象 update

第四:过 enqueueUpdate将 更新对象 update , push 到 fiber.updateQueue 当中。开始scheduleUpdateOnFiber

第五:异步调度到该类组件的 fiber 时,会通过 processUpdateQueue 函数, 更新 state。

processUpdateQueue 函数可以让 React 获得高优先级打断低优先级更新的能力。

第六: 提交,Dom 更新