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

相关推荐
twins352038 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
zqx_73 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己3 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称4 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色4 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript