React的 setState 批量更新的原理

背景

关于React 中的setState,我们常常会看见一个问题,“React中的setState是同步还是异步的?”我们经常会这样答:可异步,可同步;这篇文章就来解释一下其中的原因

现象

我们先看看,在什么样的场景中是异步的:

class App extends React.Component{
    constructor(props){
        super(props);
        this.btnClick = this.btnCLick.bind(this);
    }
    state = {
        a: 1
    }
    btnClick(){
        this.setState({
            a: this.state.a + 1
        });
        this.setState({
            a: this.state.a + 1
        });
        console.log('点击时的a的值为:',this.state.a);
    }
    render(){
        return (
            <>
                <div>hello william</div>
                <button onClick={this.btnClick}>点击按钮</button>
            </>
        )
    }
}

现象:点击按钮时的a的值为:1,在代码里我们setState了两次,值只发生了一次变化,这样看确实是异步

我们把代码稍微改动一下:

class App extends React.Component{
    constructor(props){
        super(props);
        this.btnClick = this.btnCLick.bind(this);
    }
    state = {
        a: 1
    }
    btnClick(){
        setTimeout(() => {
            this.setState({
                a: this.state.a + 1
            })
            console.log('点击时的a的值',this.state.a);
        },0)
    }
    render(){
        return (
            <>
                <div>hello william</div>
                <button onClick={this.btnClick}>点击按钮</button>
            </>
        )
    }
}

现象:点击时的a的值为:2,这是的值通过setState被修改了两次,这样看就是同步

看下源码

为什么会这样?我们看一下源码:
1、从方法的挂载开始

Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

2、enqueueSetState方法

 enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    flushPassiveEffects();
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },

当我们点击button按钮触发onClick事件的时候,会通过合成事件分发对应的回调函数,执行onClick中的内容。在onClick函数中,我们进行了一次setState。执行了enqueueSetState函数。
函数内有几个重要的步骤:

  1. createUpdate:创建了一个update对象。
  2. enqueueUpdate:创建updateQueue对象,将update对象存入到当前Fiber节点的updateQueue对象中的firstUpdate和lastUpdate中。
  3. scheduleWork:调用requestWork函数。

3、requestWork函数

function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    return;
  }

  if (isBatchingUpdates) {
    if (isUnbatchingUpdates) {
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, false);
    }
    return;
  }
  if (expirationTime === Sync) {
    performSyncWork();
  } else {
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

如果这次的setState并不是由合成事件触发的,那么isBatchingUpdates将会为false。如果为false就会直接执行performSyncWork函数了,马上对这次setState进行diff和渲染。

连续setState多次只触发一次render就是因为经过了合成事件的关系,合成事件先执行了onClick函数中的setState,修改了Fiber的updateQueue对象的任务,执行完onClick函数体后,再由合成事件让根Fiber进行渲染(。所以无论你在一个事件内触发无数次setState,也只会触发一次render。

生命周期中:在生命周期内进行setState的话当React的组件还没有渲染完成的时候,isRendering是为true。

结论

  • 异步:React 会先找到我们注册的 vnode 和 vnode 内的对应事件,从而在执行前,先把 isBatchingUpdate
    这个变量打开。只要我们的方法没完成,由于变量锁的存在,就会一直让我们的修改只停留在更新中状态内,一直不会更新到实际的 state
    上。直到我们的方法执行完,事务的后置函数就会关闭我们的 isBatchingUpdate,并执行渲染操作,至此整个批量更新就完成了。
  • 同步:setTimeout 里面会同步是由于 setTimeout会把里面的函数放到下一个宏任务内,这样就刚好跳出了事务的控制,就会显示出同步更新的情况。这里就是Javascript 的 Event-loop 机制;另外,在原生事件中,绕过了React,不会触发isBatchingUpdates变量的改变,所以也会同步进行更新渲染
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值