Skip to content

React18 一些笔记 #40

@creamidea

Description

@creamidea

简要的更新过程

以 FunctionComponent 和 一次 click 内 setState 为例

setState

省略 React 处理事件过程,直接进入 onClick 处理函数调用 setState,这里的 setState 就是 dispatchSetState 函数。

  • requestUpdateLane 根据触发事件的类型获取优先级。click 为 1,是离散事件的优先级(最开始的时候默认为 16,如果是 transition 则从 64 开始)
  • 构造 update 数据结构
    • hasEagerState 和 eagerState 是优化策略。当首次更新(首次更新判断条件:fiber.lanes === NoLane && (!fiber.alternate || fiber.alternate.lanes === NoLane),则会被设置为 true,并计算出 state 记录。如果计算的 state 和当前 state 一致,则直接退出本次更新
  • enqueueUpdate (存储到 hook.queue)
  • 调用 scheduleUpdateOnFiber 进入更新调度

scheduleUpdateOnFiber

  • markUpdateLaneFromFiberToRoot 在 sourceFiber 和 alternate 上都设置 lanes。并通过 childLanes 向上冒泡设置。最后返回 root(HostRoot)
  • markRootUpdated 也就是设置 root.pendingLanes 和 eventTimes。这里的 pendingLanes 会主要在 getNextLanes 里面使用到

if 分支处理

  • 同(root === workInProgressRoot)+渲染阶段,触发更新的处理。那么就标记 workInProgressRootRenderPhaseUpdatedLanes
  • 反之
    • root === workInProgressRoot 已经是当前更新
      • 情况一:deferRenderPhaseUpdateToNextBatch
      • 情况二:workInProgressRootExitStatus === RootSuspendedWithDelay 设置 root.suspendedLanes
    • ensureRootIsScheduled
    • 👆函数正常退出后,针对旧版的兼容处理,setTimeout 等这类内触发的更新,需要同步调用,也就是立即出发 React 渲染工作 performSyncWorkOnRoot

ensureRootIsScheduled
以 Root 为参数,安排调度 performSyncWorkOnRoot

  • markStarvedLanesAsExpired 处理是否有过期的更新(开启并发之后,防止低优先级的任务始终没有机会执行的问题)。从 pendingLanes 这个赛道里面分理出过期的任务,放到 expiredLanes 赛道内
  • getNextLanes 获取当前执行中的优先级
    • pendingLanes 是首先判断的条件。如果这个为 NoLanes,那么就直接返回 NoLanes

if 分支处理:没有获取到调度优先级

  • nextLanes === NoLanes 说明没有需要做的更新,重置相关变量,退出更新

  • getHighestPriorityLane(nextLanes) 取出最高优先级的任务作为本次更新的任务调度优先级

if 分支处理:本次更新和当前更新(如果有)比较

  • 和当前更新任务相同,直接退出更新
  • 优先级不同,并且当前存在执行中的调度任务(并发嘛,可能存在执行中的任务),则取消当前任务
  • 新建任务
    • 同步:scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
      • 如果支持 supportsMicrotasks,那么安排一下 flushSyncCallbacks (这里面会执行 performSyncWorkOnRoot,也就是将 React 工作安排在同一个浏览器 task 里面)
      • 不支持,通过 scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks),这就会被安排到下一个 task 立即执行
    • 并发:scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root)) 安排到下一个浏览器 task

performSyncWorkOnRoot

开始遍历 root 前的一些准备工作

  • flushPassiveEffects 如果存在 rootWithPendingPassiveEffects(这个会在 commitRoot 阶段被设置。如果存在说明上一次任务执行完,调度的 effect 还没有执行。那么就需要在本次更新任务执行前,执行完成。留给本次更新一个干净的环境),则执行

    • commitPassiveUnmountEffects
      • 处理删除节点的 destroy,方向是 parent -> child。detachFiberAfterEffects 方向是 child -> parent
      • 处理本次在更新 HookHasEffect 范围内的 destroy,方向是从 child -> parent
    • commitPassiveMountEffects
      • 处理本次在更新 HookHasEffect 范围内的 create,方向是从 child -> parent
  • getNextLanes 再次获取当前任务的优先级
    if 分支判断:优先级

  • 不包含 SyncLane,则将本次更新调用 ensureRootIsScheduled 重新安排,并退出本次更新,等重新调度

  • 包含 SyncLane,调用 renderRootSync 开始遍历

  • 👆函数执行完成有一个退出码

    • RootErrored 错误处理,重试一次
    • RootFatalErrored 错误处理,直接清空当前栈帧(prepareFreshStack),处理 Suspense 标记(markRootSuspended)。然后安排调度任务(ensureRootIsScheduled)。最后抛出异常退出
    • 设置如下状态,然后 commitRoot
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  • commitRoot 正常退出之后,再次安排调度 ensureRootIsScheduled

React 核心工作:Render + Commit

Render 阶段

Commit 阶段

  • 重置当前任务的一些状态
  root.callbackNode = null;
  root.callbackPriority = NoLane;
  • 处理已经完成的赛道(任务)
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
  • 其他状态
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
  • 是否有副作用,有的话安排调度 useEffect 等。👇会判断是否有同步,如果有那么就会同步执行 useEffect
if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) {
    if (!rootDoesHavePassiveEffects) {
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      // workInProgressTransitions might be overwritten, so we want
      // to store it in pendingPassiveTransitions until they get processed
      // We need to pass this through as an argument to commitRoot
      // because workInProgressTransitions might have changed between
      // the previous render and commit if we throttle the commit
      // with setTimeout
      pendingPassiveTransitions = transitions;
      scheduleCallback(NormalSchedulerPriority, () => {
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      });
    }
  }
  • 如果有内容更新,比如 DOM 变化等。首先设置执行状态 executionContext |= CommitContext。然后是 3 大步骤:

  • commitBeforeMutationEffects

    • 焦点事件等一些处理
    • 这一阶段,FunctionComponent 没有事做。不过可以提一下 ClassComponent,会调用 getSnapshotBeforeUpdate,方向是 child -> parent
  • commitMutationEffects

    • 👇都是删除的处理,方向是 parent -> child
    • 如果 FunctionComponent 有删除的状态(parentFiber.deletions),调用 destroy 函数(HookInsertion, HookLayout),删除同时会将 fiber 从树内删除(detachFiberMutation)
    • 补充:ClassComponent safelyDetachRef 和 componentWillUnmount,方向是 parent -> child
    • 补充:HostComponent safelyDetachRef。方向是 parent -> child (commitDeletionEffectsOnFiber 里面,有一个很故意的做法,case HostComponent 时没有 break 也没有 return,于是就会进入👇 HostText 的处理)
    • 补充:HostText removeChild。方向是 parent -> child

    • commitPlacement 插入 DOM (insertOrAppendPlacementNode),遍历方向是 child -> parent。DOM 节点的插入方向 parent -> child

    • 下面是更新的处理
    • 有更新(Update)处理:commitHookEffectListUnmount(HookInsertion | HookHasEffect),commitHookEffectListMount(HookInsertion | HookHasEffect), commitHookEffectListUnmount(HookLayout | HookHasEffect) 即 useLayoutEffect 的 destroy 在这里被执行
    • 补充:ClassComponent safelyDetachRef,方向是 child -> parent
    • 补充:HostComponent safelyDetachRef,如果有 Update,commitUpdate 。方向是 child -> parent
  • commitLayoutEffects

    • commitHookEffectListMount(HookLayout | HookHasEffect) 即 useLayoutEffect 的 create 在这里被执行。方向是 child -> parent
    • 补充:ClassComponent componentDidUpdate(首次就是 componentDidMount) 和 setState 的 callback
    • 补充:HostComponent 会处理 autoFocus
  • 设置 useEffect 执行需要的状态

rootDoesHavePassiveEffects = false;
// 👇这个是遍历时需要的数据
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
  • 再次安排调度 ensureRootIsScheduled,确定没有需要更新的内容

  • 如果本次任务有同步的优先级,那么同步执行 useEffect(针对的是离散型事件,比如点击,让其同步执行)

  if (
    includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
    root.tag !== LegacyRoot
  ) {
    flushPassiveEffects();
  }
  • 处理剩余的任务 remainingLanes
  • 循环调用检查,就是那个 50 次
  • 调用 flushSyncCallbacks,确保同步任务都被执行完成(If layout work was scheduled, flush it now.)
  • 结束

此时使用 updateState,来到 updateReducer 处理函数

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions