react学习系列——batchUpdate

js 复制代码
export default function Index(){
  const [ number , setNumber ] = useState(0)
  /* 同步条件下 */
  const handleClickSync = () => {
      setNumber((num) => num + 1)
      Promise.resolve().then(() => setNumber((num) => num + 1))
      setNumber((num) => num + 1)
  }
  /* 异步条件下 */
  const handleClick = () => {
      setTimeout(()=>{
        setNumber((num) => num + 1)
        Promise.resolve().then(() => setNumber((num) => num + 1))
        setNumber((num) => num + 1)
      },0)
  }
  console.log('----组件渲染----')
  return <div>
       {number}
       <button onClick={handleClickSync} >同步环境下</button>
       <button onClick={handleClick} >异步环境下</button>
   </div>
}

同步批量更新

首先在触发点击事件的时候,会将currentUpdatePriority = 2,这决定着lane优先级 = 2

js 复制代码
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
  var previousPriority = getCurrentUpdatePriority();
  var prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = null;

  try {
    setCurrentUpdatePriority(DiscreteEventPriority);
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}

之后执行点击的回调事件

第一次setState

js 复制代码
if (!didScheduleMicrotask) {
      didScheduleMicrotask = true;
      scheduleImmediateTask(processRootScheduleInMicrotask);
    }

第一次执行,将processRootScheduleInMicrotask推入微任务队列,并将didScheduleMicrotask = true

第二次setState

由于第一次执行的时候didScheduleMicrotask = true,所以第二次不会进行processRootScheduleInMicrotask

宏任务结束

现在微任务队列有[processRootScheduleInMicrotask]

第三次setState

第三次setState是异步的,微任务队列现在有两个任务[processRootScheduleInMicrotask, dispatch]

微任务执行

微任务执行首先把didScheduleMicrotask = false,标志着下一个setState也能进行processRootScheduleInMicrotask

由于lane = 2,导致mightHavePendingSyncWork = true,这个变量决定了会进入performSyncWorkOnRoot

js 复制代码
function processRootScheduleInMicrotask() {
    var nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);

    if (nextLanes === NoLane) {
      // This root has no more pending work. Remove it from the schedule. To
      // guard against subtle reentrancy bugs, this microtask is the only place
      // we do this --- you can add roots to the schedule whenever, but you can
      // only remove them here.
      // Null this out so we know it's been removed from the schedule.
      root.next = null;

      if (prev === null) {
        // This is the new head of the list
        firstScheduledRoot = next;
      } else {
        prev.next = next;
      }

      if (next === null) {
        // This is the new tail of the list
        lastScheduledRoot = prev;
      }
    } else {
      // This root still has work. Keep it in the list.
      prev = root;

      if (includesSyncLane(nextLanes)) {
        mightHavePendingSyncWork = true;
      }
    }
    
    //...
    
    flushSyncWorkOnAllRoots();
    }

之后就是processRootScheduleInMicrotask的逻辑

等到processRootScheduleInMicrotask执行完,处理下一个微任务,同样也会将processRootScheduleInMicrotask放入微任务队列

此时微任务队列有[processRootScheduleInMicrotask]

如果同时有注册了多个微任务,那么微任务队列此时是[processRootScheduleInMicrotask, dispatch,dispatch, ...]

processRootScheduleInMicrotask执行完毕,打开能processRootScheduleInMicrotask的开关,然后进行dispatch,dispatch后将processRootScheduleInMicrotask推入微任务,同时关闭processRootScheduleInMicrotask开关,后续的dispatch推不进processRootScheduleInMicrotask,因此同时有多个微任务也只能执行一次processRootScheduleInMicrotask

总结

同步批量更新下,多个同步setState只会触发一次processRootScheduleInMicrotask,微任务setState会再次触发processRootScheduleInMicrotask以及render

代码结果是进行了两次render

异步批量更新

setTimeout执行回调会在点击事件结束之后,此时currentEventPriority已经恢复,点击事件也已经结束,所以lane会取默认值32,后面几步和同步情况一样,共有两次processRootScheduleInMicrotask的微任务

js 复制代码
var updateLane = getCurrentUpdatePriority();

  if (updateLane !== NoLane) {
    return updateLane;
  } // This update originated outside React. Ask the host environment for an
  // appropriate priority, based on the type of event.
  //
  // The opaque type returned by the host config is internally a lane, so we can
  // use that directly.
  // TODO: Move this type conversion to the event priority module.


  var eventLane = getCurrentEventPriority();
  return eventLane;

第一次setState

第一次执行,将processRootScheduleInMicrotask推入微任务队列,并将didScheduleMicrotask = true

第二次setState

由于第一次执行的时候didScheduleMicrotask = true,所以第二次不会进行processRootScheduleInMicrotask

宏任务结束

现在微任务队列有[processRootScheduleInMicrotask]

第三次setState

第三次setState是异步的,微任务队列现在有两个任务[processRootScheduleInMicrotask, dispatch]

到目前为止除了lane不同,其他和同步一样

微任务执行

js 复制代码
funnction scheduleTaskForRootDuringMicrotask(root, currentTime) {
// ......
if (includesSyncLane(nextLanes)) {
    // Synchronous work is always flushed at the end of the microtask, so we
    // don't need to schedule an additional task.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }

    root.callbackPriority = SyncLane;
    root.callbackNode = null;
    return SyncLane;
  }  
}
// ......

之前同步由于lane = 2,进行到这里就返回了,异步还会往下执行

js 复制代码
else {
    // We use the highest priority lane to represent the priority of the callback.
    var existingCallbackPriority = root.callbackPriority;
    var newCallbackPriority = getHighestPriorityLane(nextLanes);

    if (newCallbackPriority === existingCallbackPriority && // Special case related to `act`. If the currently scheduled task is a
    // Scheduler task, rather than an `act` task, cancel it and re-schedule
    // on the `act` queue.
    !(ReactCurrentActQueue$2.current !== null && existingCallbackNode !== fakeActCallbackNode$1)) {
      // The priority hasn't changed. We can reuse the existing task.
      return newCallbackPriority;
    } else {
      // Cancel the existing callback. We'll schedule a new one below.
      cancelCallback(existingCallbackNode);
    }

    var schedulerPriorityLevel;

    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediatePriority;
        break;

      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingPriority;
        break;

      case DefaultEventPriority:
        schedulerPriorityLevel = NormalPriority$1;
        break;

      case IdleEventPriority:
        schedulerPriorityLevel = IdlePriority;
        break;

      default:
        schedulerPriorityLevel = NormalPriority$1;
        break;
    }

    var newCallbackNode = scheduleCallback$2(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
    root.callbackPriority = newCallbackPriority;
    root.callbackNode = newCallbackNode;
    return newCallbackPriority;
  }

首先会取消还没执行的调度,等之后新的调度一起执行

新的调度挂在在root fiber上

32的调度等级是3,超时时间是10s

js 复制代码
if (includesSyncLane(nextLanes)) {
        mightHavePendingSyncWork = true;
      }

同步的时候mightHavePendingSyncWork = true,异步执行不到,这个变量控制着能否执行performSyncWorkOnRoot,因此异步进不到重新渲染

此时微任务执行完成,队列还有一个[dispatch]

dispatch 将processRootScheduleInMicrotask推入微任务队列后执行

此时 root fiber上面挂载着刚刚新建的调度callbackNode

由于刚刚新建的调度callbackNode 和此次的调度 lane一样,因此保留上一次的调度

所以微任务触发的setState不会重新创建调度

更进一步说,即使是setTimeout触发的setState,只要优先级一样,并且挂载callbackNode的时间在上一个调度执行前,都只会存在一个render 调度

总结

异步批量更新下, setState已经不看是否进入processRootScheduleInMicrotask,而是采用了schedule,多个setState在schedule触发前都只会存在一个render任务(优先级相同)

代码结果是只进行一次render

相关推荐
一路向前的月光34 分钟前
react(9)-redux
前端·javascript·react.js
大数据追光猿1 小时前
Python中的Flask深入认知&搭建前端页面?
前端·css·python·前端框架·flask·html5
莫忘初心丶1 小时前
python flask 使用教程 快速搭建一个 Web 应用
前端·python·flask
xw51 小时前
Trae初体验
前端·trae
横冲直撞de2 小时前
前端接收后端19位数字参数,精度丢失的问题
前端
我是哈哈hh2 小时前
【JavaScript进阶】作用域&解构&箭头函数
开发语言·前端·javascript·html
摸鱼大侠想挣钱2 小时前
ActiveX控件
前端
谢尔登2 小时前
Vue 和 React 响应式的区别
前端·vue.js·react.js
后端小肥肠2 小时前
【AI编程】Java程序员如何用Cursor 3小时搞定CAS单点登录前端集成
前端·后端·cursor
酷酷的阿云2 小时前
Vue3性能优化必杀技:useDebounce+useThrottle+useLazyLoad深度剖析
前端·javascript·vue.js