不积跬步,无以至千里。

我的学习笔记

首页

前端框架

react

内容更新于: 2022-05-30 11:10:48

多次setState

前言:从几个问题开始:在react中setState是同步还是异步?多次setState之后render执行的次数和什么有关?通过阅读本文,你可以得到明确的答案,和部分的源码。

1.setState是同步还是异步?

生命周期(componentDidMount)中是异步
合成事件中是异步
setTimeout中是同步
原生事件中是同步
1-1.生命周期(componentDidMount)中是异步
如果生命周期这里的异步并不是实际意义上的异步。只是代码执行被开关限制后的一个状态延后更新。 具体在下一个问题中进行阐述。
1-2.合成事件中是异步
结果同生命周期componentDidMount,具体实现在下一个问题中阐述。
1-3.setTimeout中是同步
因为代码是同步的,并没有开关或者异步回调等方式处理它。
1-4.原生事件中是同步
同setTimeout

2.多次setState之后render执行的次数

这里探讨的问题默认expirationTime优先级是一样的。 假如改变expirationTime(优先级)其执行方式不一样。


// ReactFiberClassComponent
let i = 1
const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    // 为了测试而改变update的过期时间
    let updateExpirationTime = expirationTime + (--i)
    const update = createUpdate(updateExpirationTime);
  }
}


当优先级不一样的时候,会先更新优先级更高的update,同时执行scheduleCallbackWithExpirationTime方法注入回调,ScheduleWork会在适当的时候执行, 从而回过头执行performWork方法进行下一个状态的更新。

2-1.在生命周期中


// ...
componentDidMount () {
  this.setState({
    age: 1
  })
  this.setState({
    age: 2
  })
  this.setState({
    age: 3
  })
}
// ...


答案是一次,确切的说是优先级一样所以render一次。
首先componentDidMount生命周期是在commit阶段触发的。
此时commit并未结束, 变量isCommitting为true、isRendering也为true.
setState是去执行函数scheduleWork


const classComponentUpdater = {
  enqueueSetState () {
    // ...
    flushPassiveEffects();
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  }
}


scheduleWork --> requestWork 因为此时isRendering为true所以并未执行之后的步骤。


function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  addRootToSchedule(root, expirationTime);
  // console.log('isRendering: ', isRendering)
  // last
  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }
  // ...
}


所以之后的render函数自然没有执行。
那之后的步骤是如何继续的呢
在开始执行performWork的循环中 在执行完performWorkOnRoot之后回去检查是否还有有过期时间的root


function performWork () {
  // ...
  while (
    nextFlushedRoot !== null &&
    nextFlushedExpirationTime !== NoWork &&
    minExpirationTime <= nextFlushedExpirationTime
  ) {
  
    performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false); 
    // root.expirationTime
    findHighestPriorityRoot();
  }
  // ...


而之前说到的componentDidMount是在commit阶段被执行的。同时setState触发scheduleWork中 会执行markPendingPriorityLevel方法将root的expirationTime重新标记。上述函数中的while 循环会继续执行,更新之后的状态,所以说render只会执行一次。

2-2.在setTimeout中

与setState执行的次数一致


class Main extends React.Component {
  componentDidMount () {
    setTimeout(() => {
      this.setState({
        age: 1
      })
      Logger.info('age: ', this.state.age) 
      // 1
      this.setState({
        age: 2
      })
      Logger.info('age: ', this.state.age)
      // 2
      this.setState({
        age: 3
      })
      Logger.info('age: ', this.state.age)
      // 3
    })
  }
  render () {
    const { age, gender } = this.state
    Logger.info('render age: ', this.state.age)
    // 0
    // 1
    // 2
    // 3
    return React.createElement('div', {
      onClick: this.onClick.bind(this)
    }, `age: ${age} -- gender: ${gender}`)
  }
}
// 这里setState引起的render 会执行3次


2-3.在合成事件中

执行一次 首先在requestWork函数中也是开关, 阻止之后的更新。


// requestWork 函数
if (isBatchingUpdates) {
  // Flush work at the end of the batch.
  // isUnbatchingUpdates 在当前这个问题中 false
  if (isUnbatchingUpdates) {
    // ...unless we're inside unbatchedUpdates, 
    // in which case we should
    // flush it now.
    nextFlushedRoot = root;
    nextFlushedExpirationTime = Sync;
    performWorkOnRoot(root, Sync, false);
  }
  return;
}


在函数interactiveUpdates的finally中执行performSyncWork


function dispatchInteractiveEvent(topLevelType, nativeEvent) {
  // Logger.info('dispatchInteractiveEvent', topLevelType)
  interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}



// 函数interactiveUpdates
try {
  return runWithPriority(UserBlockingPriority, () => {
    return fn(a, b);
  });
} finally {
  isBatchingUpdates = previousIsBatchingUpdates;
  if (!isBatchingUpdates && !isRendering) {
    MainLogger.step(
      'ReactFiberScheduler interactiveUpdates', 
      'finally 执行 performSyncWork()'
    )
    performSyncWork();
  }
}


performSyncWork --> performWork

总结

react的这种机制可以有效的防止多个setState产生的不必要的开支。

本文结束