• React18源码: reconciler执行流程


    reconciler执行流程


    1 )概述

    • 此处先归纳一下react-reconciler包的主要作用,将主要功能分为4个方面:
      • 输入:暴露api函数(如:scheduleUpdateOnFiber), 供给其他包(如react包)调用
      • 注册调度任务:与调度中心(scheduler包)交互,注册调度任务task,等待任务回调
      • 执行任务回调:在内存中构造出fiber树,同时与渲染器(react-dom)交互,在内存中创建出与fiber对应的DOM节点
      • 输出:与渲染器(react-dom)交互,渲染DOM节点
    • 图中的1,2,3,4步骤可以反映react-reconciler包从输入到输出的运作流程
    • 这是一个固定流程,每一次更新都会运行

    2 )输入

    • 在ReactFiberWorkLoop.js中,承接输入的函数只有scheduleUpdateOnFiber

    • 在 react-reconciler 对外暴露的api函数中,只要涉及到需要改变fiber的操作(无论是首次渲染或后续更新操作)

    • 最后都会间接调用 scheduleUpdateOnFiber

    • 所以scheduleUpdateOnFiber函数是输入链路中的必经之路

      //唯一接收输入信号的函数
      export function scheduleUpdateOnFiber(
        fiber: Fiber,
        lane: Lane,
        eventTime: number,
      ) {
        // ... 省略部分无关代码
        const root = markUpdateLaneFromFiberToRoot(fiber, lane);
        // 同步
        if (lane === SyncLane) {
          if (
            (executionContext & LegacyUnbatchedContext) !== NoContext &&
            (executionContext & (RenderContext | CommitContext)) === NoContext
          ) {
            // 直接进行fiber构造
            performSyncWorkOnRoot(root);
          } else {
            // 注册调度任务,经过`Scheduler'包的调度,间接进行`fiber构造'
            ensureRootIsScheduled(root, eventTime);
          }
        } else {
          // 注册调度任务,经过`Scheduler`包的调度,间接进行`fiber构造`
          ensureRootIsScheduled(root, eventTime);
        }
      }
      
      • 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
    • 逻辑进入到scheduleUpdateOnFiber之后,后面有2种可能:

      • 1.不经过调度,直接进行fiber构造.
      • 2.注册调度任务,经过Scheduler包的调度,间接进行fiber构造.

    2 )注册调度任务

    与输入环节紧密相连,scheduleUpdateOnFiber函数之后,立即进入 ensureRootIsScheduled 函数

    // ... 省略部分无关代码
    function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
      //前半部分:判断是否需要注册新的调度
      const existingCallbackNode - root. callbackNode;
      const nextlanes = getNextLanes(
        root,
        root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
      );
      const newCallbackPriority = returnNextLanesPriority();
      if (nextLanes === NoLanes) {
        return;
      }
      if (existingCallbackNode !== null) {
        const existingCallbackPriority = root.callbackPriority;
        if (existingCallbackPriority === newCallbackPriority) {
          return;
        }
        cancelCallback(existingCallbackNode);
      }
      // 后半部分:注册调度任务
      let newCallbackNode;
      if (newCallbackPriority === SyncLanePriority){
        newCallbackNode = scheduleSyncCallback(
          performSyncWorkOnRoot.bind(null, root),
        );
      } else if (newCallbackPriority === SyncBatchedLanePriority) {
        newCallbackNode = scheduleCallback(
          ImmediateSchedulerPriority,
          performSyncWorkOnRoot.bind(null, root),
        );
      } else {
        const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
          newCallbackPriority,
        );
        newCallbackNode = scheduleCallback(
          schedulerPriorityLevel,
          performConcurrentWorkOnRoot.bind(null, root),
        );
      }
      root.callbackPriority = newCallbackPriority;
      root.callbackNode = newCallbackNode;
    }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • ensureRootIsScheduled的逻辑很清晰,分为2部分:
      • 1.前半部分:判断是否需要注册新的调度(如果无需新的调度,会退出函数)
      • 2.后半部分:注册调度任务
        • performSyncWorkOnRoot 或 performConcurrentWorkOnRoot 被封装到了任务回调 (schedulecallback)
        • 等待调度中心执行任务,任务运行其实就是执行 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot

    3 )执行任务回调

    • 任务回调,实陈上就是执行 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot

    • 简单看一下它们的源码将主要逻辑剥离出来,单个函数的代码量并不多

      //..,省略部分无关代码
      function performSyncWorkOnRoot(root) {
        let lanes;
        let exitStatus;
      
        lanes = getNextLanes(root, NoLanes);
        // 1. fiber树构造
        exitStatus = renderRootSync(root, lanes);
      
        // 2. 异常处理:有可能fiber构造过程中出现异常
        if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
          // ...
        }
      
        // 3. 输出:渲染fiber树
        const finishedWork: Fiber = (root.current.alternate: any);
        root.finishedwork = finishedWork;
        root.finishedLanes = lanes;
        commitRoot(root);
      
        // 退出前再次检测,是否还有其他更新,是否需要发起新调度
        ensureRootIsScheduled(root, now());
        return null;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • performSyncWorkOnRoot 的逻辑很清晰,分为3部分:

      • fiber 树构造

      • 异常处理: 有可能fiber构造过程中出现异常

      • 调用输出

        // ... 省略部分无关代码
        function performConcurrentWorkOnRoot(root) {
          const originalCallbackNode = root.callbackNode;
        
          // 1、刷新pending状态的effects,有可能某些effect会取消本次任务
          const didFlushPassiveEffects = flushPassiveEffects();
          if (didFlushPassiveEffects) {
            if (root.callbackNode !== originalCallbackNode) {
              // 任务被取消,退出调用
              return null;
            } else {
              // Current task was not canceled. Continue.
            }
          }
        
          // 2.获取本次渲染的优先级
          let lanes = getNextLanes(
            root,
            root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
          );
        
          // 3.构造fiber树
          let exitStatus = renderRootConcurrent(root, lanes);
          if (
            includesSomeLane(
              workInProgressRootIncludedLanes,
              workInProgressRootUpdatedLanes,
            )
          ) {
            // 如果在render过程中产生了新的update,且新update的优先级与最初render的优先级有交集
            // 那么最初render无效,丢弃最初render的结果,等待下一次调度
            prepareFreshStack(root, NoLanes);
          } else if (exitStatus !== RootIncomplete) {
            // 4、异常处理:有可能fiber构造过程中出现异常
            if (exitStatus == RootErrored) {
              // ...
            }
            const finishedWork: Fiber = (root.current. alternate: any);
            root.finishedWork = finishedwork;
            root.finishedLanes = lanes;
            // 5.输出:渲染fiber树
            finishConcurrentRender(root, exitStatus, lanes);
          }
        
          // 退出前再次检测,是否还有其他更新,是否需要发起新调度
          ensureRootIsScheduled(root, now());
          if (root.callbackNode === originalCallbackNode) {
            // 渲染被阻断,返回一个新的performConcurrentWorkOnRoot函数。等待下一次调用
            return performConcurrentWorkOnRoot.bind(null, root);
          }
          return null;
        }
        
        • 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
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
    • performConcurrentWorkOnRoot 的逻辑与 performSyncWorkOnRoot 的不同之处在于

    • 对于可中断渲染的支持:

      • 1.调用 performConcurrentWorkOnRoot 函数时,首先检查是否处于 render 过程中,是否需要恢复上一次渲染
      • 2.如果本次渲染被中断,最后返回一个新的 performConcurrentWorkOnRoot 函数,等待下一次调用

    4 )输出

    // ... 省略部分无关代码
    function commitRootImpl(root, renderPriorityLevel) {
      // 设置局部变量
      const finishedWork = root.finishedWork;
      const lanes - root. finishedLanes;
    
      // 清空FiberRoot对象上的属性
      root.finishedWork = null;
      root.finishedLanes = NoLanes;
      root.callbackNode = null;
      
      // 提交阶段
      let firstEffect = finishedWork.firstEffect;
      if (firstEffect !== null) {
        const prevExecutionContext - executionContext;
        executionContext |= CommitContext;
        // 阶段1:dom突变之前
        nextEffect = firstEffect;
        do {
          commitBeforeMutationEffects();
        } while (nextEffect !== null);
    
        // 阶段2:dom突变,界面发生改变
        nextEffect = firstEffect;
        do {
          commitMutationEffects(root, renderPriorityLevel);
        } while (nextEffect !== null);
        root.current = finishedWork;
        
        // 阶段3:layout阶段,调用生命周期componentDidUpdate和回调函数等
        nextEffect = firstEffect;
        do{
          commitLayoutEffects(root, lanes);
        } while (nextEffect !== null);
        nextEffect = null;
        executionContext = prevExecutionContext;
      }
      ensureRootIsScheduled(root, now());
      return null;
    }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 在输出阶段,commitRoot 的实现逻辑是在 commitRootImpl 函数中
    • 其主要逻辑是处理副作用队列,将最新的fiber树结构反映到DOM上
    • 核心逻辑分为3个步骤:
      • 1.commitBeforeMutationEffects
        • dom变更之前,主要处理副作用队列中带有Snapshot, Passive标记的fiber节点
      • 2.commitMutationEffects
        • dom变更,界面得到更新.主要处理副作用队列中带有
        • Placement,Update,Deletion, Hydrating标记的fiber节点
      • 3.commitLayoutEffects
        • dom变更后,主要处理副作用队列中带有 update | Callback 标记的fiber节点.
    • 这块流程参考 React16版本的流程,看下不同之处
    • 所以,整个 reconciler 的执行过程中,核心做了2个事情
      • 1 )Render (基于task, 可以被打断, 可以被打断的前提是基于渲染 mode)
        • 初始化 fiber
        • 更新 fiber
      • 2 )commit
        • dom 变更之前
        • dom 变更
        • dom 更新之后
  • 相关阅读:
    甘特图来搞定跨部门协作难的问题!项目经理必备
    vue 封装 瀑布流
    git命令的操作
    Johnson 全源最短路
    产品第一次兼项目经理积累了经验也踩过坑
    java毕业设计超市购物数据管理系统mybatis+源码+调试部署+系统+数据库+lw
    从零开发一款图片编辑器Mitu-Dooring
    Windows安装mamba全流程(全网最稳定最成功)
    C++STL
    前端项目规范化
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/136243747