• React update和enqueueUpdate


    源码版本: v17.0.1,
    官方源码地址
    源码调试教程
    调试的源码
    画图软件

    ReactDOM.render源码解析

    上文中updateContainer 函数中提到了updateupdateQueue

    每次更新操作就会创建update对象(ReactDOM.render和setState), 里面记录了组件的状态的变化

    createUpdate 函数

    源码地址

    export type Update<State> = {|
      // TODO: Temporary field. Will remove this by storing a map of
      // transition -> event time on the root.
      eventTime: number,
      lane: Lane,
    
      tag: 0 | 1 | 2 | 3,
      payload: any,
      callback: (() => mixed) | null,
    
      next: Update<State> | null,
    |};
    
    export function createUpdate(eventTime: number, lane: Lane): Update<*> {
      const update: Update<*> = {
    
        // 获取更新触发的时间
        // 它的获取方法requestEventTime(), 其实本质就是performance.now()
        eventTime,
    
        // 计算当前节点lane(优先级)
        lane,
    
        tag: UpdateState,
    
        // 更新的内容,比如setState接收的第一个参数
        payload: null,
    
        // 对应的回调,比如setState({}, callback )
        callback: null,
    
        // 指向下一个更新, 页面中我们可能多次触发setState
        next: null,
      };
      return update;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    获取更新触发时间

    requestEventTime 函数

    源码地址

    获取更新触发的时间
    也就是update对象中eventTime, 以updateContainer为例, 里面有requestEventTime


    react 就是通过requestEventTime函数来创建一个currentEventTime, 用于标识更新的任务触发的时间, 对于相同的时间的任务, 就会批量去执行
    同样优先级的任务, currentEventTime数值越小, 越早被执行

    export function requestEventTime() {
    	// RenderContext: 代表react正在计算更新
    	// CommitContext: 代表react正在提交更新
    	// react更新分了两步, render 和 commit阶段
    	if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    		// react 正在执行中, 直接返回当前的时间
    		return now();
    	}
    
    	// 如果不在 react执行过程中
    	if (currentEventTime !== NoTimestamp) {
    		// 返回上一次currentEventTime
    		return currentEventTime;
    	}
    
    	// react 更新被打断后的首次更新, 计算新的currentEventTime
    	currentEventTime = now();
    	
    	return currentEventTime;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    now 函数

    export const now =
      initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
    
    • 1
    • 2

    当前后的更新任务时间差小于 10ms 时,直接采用上次的 Scheduler_now,这样可以抹平 10ms 内更新任务的时间差, 有利于批量更新

    获取任务优先级

    requestUpdateLane 函数

    源码地址

    获取更新触发的时间
    updateContainer为例, 里面有requestUpdateLane

    简要概括事件优先级:

    • 离散事件(DiscreteEvent):click、keydown、focusin等,这些事件的触发不是连续的,优先级为0。
    • 用户阻塞事件(UserBlockingEvent):drag、scroll、mouseover等,特点是连续触发,阻塞渲染,优先级为1。
    • 连续事件(ContinuousEvent):canplay、error、audio标签的timeupdate和canplay,优先级最高,为2。
    export function requestUpdateLane(fiber: Fiber): Lane {
    	// 根据记录下的事件的优先级,获取任务调度的优先级
    	const schedulerPriority = getCurrentPriorityLevel();
    
    	let lane;
    	if (
    		// TODO: Temporary. We're removing the concept of discrete updates.
    		(executionContext & DiscreteEventContext) !== NoContext &&
    		schedulerPriority === UserBlockingSchedulerPriority
    	) {
    		 // 如果是用户阻塞级别的事件,则通过InputDiscreteLanePriority 计算更新的优先级 lane
    		lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
    	} else {
    
    		// 否则依据事件的优先级计算 schedulerLanePriority
    		const schedulerLanePriority =
    			schedulerPriorityToLanePriority(schedulerPriority);
    
    		if (decoupleUpdatePriorityFromScheduler) {
    			const currentUpdateLanePriority = getCurrentUpdateLanePriority();
    		}
    		// 根据计算得到的 schedulerLanePriority,计算更新的优先级 lane
    		lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
    	}
    	return lane;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    findUpdateLane 这个方法中,按照事件的类型,匹配不同级别的 lane,事件类型的优先级,值越高,代表优先级越高:

    源码地址

    export const SyncLanePriority: LanePriority = 15;
    export const SyncBatchedLanePriority: LanePriority = 14;
    const InputDiscreteHydrationLanePriority: LanePriority = 13;
    export const InputDiscreteLanePriority: LanePriority = 12;
    const InputContinuousHydrationLanePriority: LanePriority = 11;
    export const InputContinuousLanePriority: LanePriority = 10;
    const DefaultHydrationLanePriority: LanePriority = 9;
    export const DefaultLanePriority: LanePriority = 8;
    const TransitionHydrationPriority: LanePriority = 7;
    export const TransitionPriority: LanePriority = 6;
    const RetryLanePriority: LanePriority = 5;
    const SelectiveHydrationLanePriority: LanePriority = 4;
    const IdleHydrationLanePriority: LanePriority = 3;
    const IdleLanePriority: LanePriority = 2;
    const OffscreenLanePriority: LanePriority = 1;
    export const NoLanePriority: LanePriority = 0;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    将update对象加入队列中 enqueueUpdate

    源码地址

    type SharedQueue<State> = {|
      pending: Update<State> | null,
    |};
    
    export type UpdateQueue<State> = {|
      baseState: State,
      firstBaseUpdate: Update<State> | null,
      lastBaseUpdate: Update<State> | null,
      shared: SharedQueue<State>,
      effects: Array<Update<State>> | null,
    |};
    
    export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
      // 获取当前fiber身上的updateQueue对象
      const updateQueue = fiber.updateQueue; 
      if (updateQueue === null) {
        // updateQueue为空, 说明当前的fiber还没有渲染, 直接退出即可
        return;
      }
    
      const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
      const pending = sharedQueue.pending; // shared.pending指向该链表的最后一个update对象
      if (pending === null) {
        // This is the first update. Create a circular list.
        // 说明是首次更新, 需要创建循环链表
        update.next = update;
      } else {
        // 不是首次更新, 那就把update对象插入到循环链表中
        update.next = pending.next;
        pending.next = update;
      }
      sharedQueue.pending = update;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    分析:
    第一次:
    在这里插入图片描述
    第二次:
    在这里插入图片描述
    第三次:
    在这里插入图片描述

    参考文章:

    • https://juejin.cn/post/7019254208830373902
  • 相关阅读:
    学习突围2 - 关于高效学习的方法
    Spring Boot项目整合swagger进行接口测试
    Java(97)Java的JNI学习(一)
    人工智能之地形导航系统
    如何编写难以维护的 React 代码?耦合通用组件与业务逻辑
    Redis高可用实战之Replication
    linux系统安全配置命令详解
    Android 指纹验证
    HTTP协议发展史
    2327. 知道秘密的人数;1722. 执行交换操作后的最小汉明距离;2537. 统计好子数组的数目
  • 原文地址:https://blog.csdn.net/qq_39583550/article/details/125905341