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

相关推荐
酷酷的阿云4 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205876 分钟前
web端手机录音
前端
齐 飞12 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹29 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0011 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
木舟10092 小时前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43912 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢2 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js