找回密码
 立即注册
首页 业界区 安全 React源码阅读(2)-fiber核心构建原理

React源码阅读(2)-fiber核心构建原理

凉砧掌 前天 18:43
React源码阅读(2)-fiber核心构建原理

文章主要介绍了 React 源码中 fiber 核心的构建原理,包括执行上下文、入口函数、构建阶段(如 performSyncWorkOnRoot 中的树构建和提交)、提交阶段(准备、提交的三个子阶段、渲染完成后的操作)以及调度注册等,还展示了手写 fiber-dom 的部分代码。
关联问题: Fiber 构建如何优化 手写代码能扩展吗 调度细节如何理解
AI智能总结首次生成速度较慢, 请耐心等待
react-reconciler核心逻辑理解并手写

想了很久该怎么写,才能写出来让大家看懂,就我们先抛开复杂的车道优先级和调度优先级以及调度等级以及react大量的时间操作等到后面分析18源码的时候一起讲。就从最简单的开始手写fiber-dom。
1.react构建fiber核心代码

对应源码位于ReactFiberWorkLoop.js,
我们先大致了解一下整体的走向图。
1.webp

1.1 入口

在全局变量中有executionContext, 代表渲染期间的执行上下文, 它也是一个二进制表示的变量, 通过位运算进行操作. 在源码中一共定义了 8 种执行栈:
  1. type ExecutionContext = number;
  2. export const NoContext = /*             */ 0b0000000;
  3. const BatchedContext = /*               */ 0b0000001;
  4. const EventContext = /*                 */ 0b0000010;
  5. const DiscreteEventContext = /*         */ 0b0000100;
  6. const LegacyUnbatchedContext = /*       */ 0b0001000;
  7. const RenderContext = /*                */ 0b0010000;
  8. const CommitContext = /*                */ 0b0100000;
复制代码
这里我们只分析Legacy模式(对启动模式不清楚的可以看看开篇), Legacy模式下的首次更新, 会直接进行构建,然后经过提交到页面上,并不会进入去注册调度任务。
接下来从输入(阅读须知)开始我们看核心的输入源码
  1. export function scheduleUpdateOnFiber(
  2.   fiber: Fiber,
  3.   lane: Lane,
  4.   eventTime: number,
  5. ) {
  6.   if (lane === SyncLane) {
  7.     // legacy模式
  8.     if (
  9.       // 首次无上下文也无渲染上下文
  10.       (executionContext & LegacyUnbatchedContext) !== NoContext &&
  11.       (executionContext & (RenderContext | CommitContext)) === NoContext
  12.     ) {
  13.       // 初次更新
  14.       performSyncWorkOnRoot(root);
  15.     } else {
  16.       // 后续的更新,注册调度任务
  17.       ensureRootIsScheduled(root, eventTime);
  18.     }
  19.   }
  20. }
复制代码
在 render 过程中, 每一个阶段都会改变executionContext(render 之前, 会设置executionContext |= RenderContext; commit 之前, 会设置executionContext |= CommitContext), 假设在render过程中再次发起更新(如在UNSAFE_componentWillReceiveProps生命周期中调用setState)则可通过executionContext来判断当前的render状态。上面我们已经看到了入口的函数scheduleUpdateOnFiber,接下来我们分析入口函数中的2个关键的函数performSyncWorkOnRoot和ensureRootIsScheduled。
1.2 performSyncWorkOnRoot

首先我们来分析performSyncWorkOnRoot,里面的主要做的事情就是做fiber树的构建以及最后的提交commitRoot。在这个函数中我们也可以分为2个阶段,一个阶段是fiber树的构建,另一个阶段是commitRoot的提交(输出)。
阶段1.fiber树的构建
  1. function performSyncWorkOnRoot(root) {
  2.   let lanes;
  3.   let exitStatus;
  4.   // workInProgressRoot当前构建的任务
  5.   if (
  6.     root === workInProgressRoot &&
  7.     includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
  8.   ) {
  9.     // 初次构建的时候从root节点开始, 形成一个完成的fiberdom树
  10.     lanes = workInProgressRootRenderLanes;
  11.     exitStatus = renderRootSync(root, lanes);
  12.   } else {
  13.     // 1. 获取本次render的优先级, 初次构造返回 NoLanes
  14.     lanes = getNextLanes(root, NoLanes);
  15.     // 2. 从root节点开始, 至上而下更新,形成一个完成的fiberdom树
  16.     exitStatus = renderRootSync(root, lanes);
  17.   }
  18.   // 将最新的fiber树挂载到root.finishedWork节点上
  19.   const finishedWork: Fiber = (root.current.alternate: any);
  20.   root.finishedWork = finishedWork;
  21.   root.finishedLanes = lanes;
  22.   // 进入commit阶段,渲染到页面上
  23.   commitRoot(root);
  24. }
复制代码
实际我们看到不管是初次构建还是后续更新都会走到renderRootSync上去构建fiber树,那它又做了什么那
  1. function renderRootSync(root: FiberRoot, lanes: Lanes) {
  2.   const prevExecutionContext = executionContext;
  3.   executionContext |= RenderContext;
  4.   // 如果fiberRoot变动, 或者update.lane变动,update(链表我们后面讲)
  5.   if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
  6.     // 刷新栈帧
  7.     prepareFreshStack(root, lanes);
  8.   }
  9.   do {
  10.     try {
  11.       // 核心代码循环构建fiber
  12.       workLoopSync();
  13.       break;
  14.     } catch (thrownValue) {
  15.       handleError(root, thrownValue);
  16.     }
  17.   } while (true);
  18.   executionContext = prevExecutionContext;
  19.   // 重置全局变量, 表明render结束
  20.   workInProgressRoot = null;
  21.   workInProgressRootRenderLanes = NoLanes;
  22.   return workInProgressRootExitStatus;
  23. }
复制代码
如果是初读源码我们可以只关心一个地方那就是workLoopSync,当然这是在legacy模式下,concurrent模式下走的是workLoopConcurrent去实现时间分片和循环可中断。下一步我们就可以看workLoopSync
  1. function workLoopSync() {
  2.   while (workInProgress !== null) {
  3.     performUnitOfWork(workInProgress)
  4.   }
  5. }
复制代码
这里就相当于把循坏构建fiberDom树了,而前面备注中也写到了workInProgress指向当前构建的任务或者说节点更适合一些,来到构建fiberDom树的performUnitOfWork.
  1. function performUnitOfWork(unitOfWork: Fiber): void {
  2.   // 指向当前页面的`fiber`节点. 初次构造时, 页面还未渲染, 就是空的
  3.   const current = unitOfWork.alternate;
  4.   let next;
  5.   if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
  6.     startProfilerTimer(unitOfWork);
  7.     next = beginWork(current, unitOfWork, subtreeRenderLanes);
  8.     stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  9.   } else {
  10.     //构建出最终的fiberDom,并且返回dom结构的chilrden处理之后的child
  11.     next = beginWork(current, unitOfWork, subtreeRenderLanes);
  12.   }
  13.   unitOfWork.memoizedProps = unitOfWork.pendingProps;
  14.   if (next === null) {
  15.     // 没新节点的时候就可以准备回溯洛
  16.     completeUnitOfWork(unitOfWork);
  17.   } else {
  18.     workInProgress = next;
  19.   }
  20.   ReactCurrentOwner.current = null;
  21. }
复制代码
进入beginWork,这里主要做的事情是通过ReactElement去完成fiber树的更新和构建,他的细节是在在于针对每一个fiber类型分别有不同的update函数去做处理,值得一提的是这里基本完成了fiber本身基础状态的一些设置,我觉得我们这个阅读阶段我们先关注主流程的事情,也就是updateXXX的处理。
  1. function beginWork(
  2.   current: Fiber | null,
  3.   workInProgress: Fiber,
  4.   renderLanes: Lanes,
  5. ): Fiber | null {
  6.   // 忽略代码
  7.   // 这里是对于所有fiber类型的不同处理,我们关心不同tag怎么处理的就行了,等下手写的时候写简单一点
  8.   switch (workInProgress.tag) {
  9.     case IndeterminateComponent: {
  10.       return mountIndeterminateComponent(
  11.         current,
  12.         workInProgress,
  13.         workInProgress.type,
  14.         renderLanes,
  15.       );
  16.     }
  17.     case LazyComponent: {
  18.       const elementType = workInProgress.elementType;
  19.       return mountLazyComponent(
  20.         current,
  21.         workInProgress,
  22.         elementType,
  23.         updateLanes,
  24.         renderLanes,
  25.       );
  26.     }
  27.     case FunctionComponent: {
  28.       const Component = workInProgress.type;
  29.       const unresolvedProps = workInProgress.pendingProps;
  30.       const resolvedProps =
  31.         workInProgress.elementType === Component
  32.           ? unresolvedProps
  33.           : resolveDefaultProps(Component, unresolvedProps);
  34.       return updateFunctionComponent(
  35.         current,
  36.         workInProgress,
  37.         Component,
  38.         resolvedProps,
  39.         renderLanes,
  40.       );
  41.     }
  42.     case ClassComponent: {
  43.       const Component = workInProgress.type;
  44.       const unresolvedProps = workInProgress.pendingProps;
  45.       const resolvedProps =
  46.         workInProgress.elementType === Component
  47.           ? unresolvedProps
  48.           : resolveDefaultProps(Component, unresolvedProps);
  49.       return updateClassComponent(
  50.         current,
  51.         workInProgress,
  52.         Component,
  53.         resolvedProps,
  54.         renderLanes,
  55.       );
  56.     }
  57.     case HostRoot:
  58.       return updateHostRoot(current, workInProgress, renderLanes);
  59.     case HostComponent:
  60.       return updateHostComponent(current, workInProgress, renderLanes);
  61.     case HostText:
  62.       return updateHostText(current, workInProgress);
  63.    //忽略代码
  64.   }
  65. }
复制代码
举个例子updateHostRoot,这里我就可以看到一些具体的fiber构建的细节了。
  1. function updateHostRoot(current, workInProgress, renderLanes) {
  2.   // 1. 状态计算, 更新整合到 workInProgress.memoizedState中来
  3.   const updateQueue = workInProgress.updateQueue;
  4.   const nextProps = workInProgress.pendingProps;
  5.   const prevState = workInProgress.memoizedState;
  6.   const prevChildren = prevState !== null ? prevState.element : null;
  7.   cloneUpdateQueue(current, workInProgress);
  8.   // 遍历updateQueue.shared.pending, 提取有足够优先级的update对象, 计算出最终的状态 workInProgress.memoizedState
  9.   processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  10.   const nextState = workInProgress.memoizedState;
  11.   // 2. 获取下级`ReactElement`对象
  12.   const nextChildren = nextState.element;
  13.   const root: FiberRoot = workInProgress.stateNode;
  14.   if (root.hydrate && enterHydrationState(workInProgress)) {
  15.     // ...服务端渲染相关, 此处省略
  16.   } else {
  17.     // 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)
  18.     reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  19.   }
  20.   // 返回child进行下一次循坏
  21.   return workInProgress.child;
  22. }
复制代码
而在此之后那我们就进去一个由下往上的回溯阶段(递归中的归),也就是completeUnitOfWork(performUnitOfWork中的方法),同样我们关注在当前阅读这个主流程上函数做了什么,首先他给stateNode去指向了一个Dom实例,设置属性绑定事件,设置flag标记,紧接着处理副作用链表队列(就是更新头尾指针,emm有数据结构算法基础的可以看看),然后有一些兄弟节点子节点的判断。
  1. function completeUnitOfWork(unitOfWork: Fiber): void {
  2.   let completedWork = unitOfWork;
  3.   // 外层循环控制并移动指针(`workInProgress`,`completedWork`等)
  4.   do {
  5.     const current = completedWork.alternate;
  6.     const returnFiber = completedWork.return;
  7.     if ((completedWork.flags & Incomplete) === NoFlags) {
  8.       let next;
  9.       // 1. 处理Fiber节点, 关联Fiber节点和dom对象, 绑定事件
  10.       next = completeWork(current, completedWork, subtreeRenderLanes); // 处理单个节点
  11.       if (next !== null) {
  12.         // 子节点, 则回到beginWork阶段进行处理
  13.         workInProgress = next;
  14.         return;
  15.       }
  16.       // 重置子节点的优先级
  17.       resetChildLanes(completedWork);
  18.       if (
  19.         returnFiber !== null &&
  20.         (returnFiber.flags & Incomplete) === NoFlags
  21.       ) {
  22.         // 2. 收集当前Fiber节点以及其子树的副作用effects
  23.         // 2.1 把子节点的副作用队列添加到父节点上,只有父节点的firstEffect不存在时
  24.         // 才能将父节点的firstEffect指向当前节点的副作用单向链表头
  25.         if (returnFiber.firstEffect === null) {
  26.           //让父节点的firstEffect指向当前节点的firstEffect
  27.           returnFiber.firstEffect = completedWork.firstEffect;
  28.         }
  29.         //当前节点不存在副作用链表才加
  30.         if (completedWork.lastEffect !== null) {
  31.           if (returnFiber.lastEffect !== null) {
  32.             returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
  33.           }
  34.           // 将当前节点加到副作用链表中
  35.           returnFiber.lastEffect = completedWork.lastEffect;
  36.         }
  37.         // 2.2 如果当前fiber节点有副作用, 将其添加到子节点的副作用队列之后.
  38.         const flags = completedWork.flags;
  39.         if (flags > PerformedWork) {
  40.           // 忽略PerformedWork
  41.           // 将当前节点添加到副作用链表尾部
  42.           if (returnFiber.lastEffect !== null) {
  43.             returnFiber.lastEffect.nextEffect = completedWork;
  44.           } else {
  45.             returnFiber.firstEffect = completedWork;
  46.           }
  47.           returnFiber.lastEffect = completedWork;
  48.         }
  49.       }
  50.     }
  51.     const siblingFiber = completedWork.sibling;
  52.     if (siblingFiber !== null) {
  53.       // 如果有兄弟节点, 返回之后再次进入beginWork阶段
  54.       workInProgress = siblingFiber;
  55.       return;
  56.     }
  57.     // 移动指针, 指向下一个节点
  58.     completedWork = returnFiber;
  59.     workInProgress = completedWork;
  60.   } while (completedWork !== null);
  61.   // 已回溯到根节点, 设置workInProgressRootExitStatus = RootCompleted
  62.   if (workInProgressRootExitStatus === RootIncomplete) {
  63.     workInProgressRootExitStatus = RootCompleted;
  64.   }
  65. }
复制代码
到这那我们已经就可以走到上面的commitRoot将内存里的fiber树输出到react-dom了。
阶段2.fiber树的输出

在我们的构建阶段结束后,会在performSyncWorkOnRoot、finishConcurrentRender中的fiberRoot传给commitRoot,去开始commit提交阶段,此时已经到了fiber的最终输出阶段,经过了一系列优先级转换,最终会执行commitRootImpl。
  1. function commitRoot(root) {
  2.   // 渲染等级
  3.   const renderPriorityLevel = getCurrentPriorityLevel()
  4.   // 调度等级
  5.   runWithPriority(ImmediateSchedulerPriority, commitRootImpl.bind(null, root, renderPriorityLevel))
  6.   return null
  7. }
复制代码
commitRootImpl主要工作就是遍历effect数组去添加对应的标记,最终交给react-dom执行,commit有三个大阶段:1.提交准备阶段。2.提交阶段,而提交阶段中的三个while循坏下分别又对应3个子阶段:before mutation阶段。mutation阶段。layout阶段。3.提交后。代码较长我们分成3部分解释。
1:准备阶段。

我们大概做了这几个操作:
1.判断是否有未执行的useEffect,如果有先执行,等待我们上一轮的useEffect执行完后才继续我们commit。
2.更新副作用队列,将根节点加到尾部,取出 firstEffect,也就是第一个需要被处理的 fiber 节点。
3.接着判断如果存在 firstEffect,会将 firstEffect 指向 nextEffect,开始三个子阶段。
  1. function commitRootImpl(root, renderPriorityLevel) {
  2.   // 准备提交阶段
  3.   // 判断rootWithPendingPassiveEffects,就是说先判断是否有未执行的useEffect,如果有先执行,等待我们上一轮的useEffect执行完后才继续我们commit。
  4.   do {
  5.     //
  6.     flushPassiveEffects()
  7.   } while (rootWithPendingPassiveEffects !== null)
  8.   // 忽略代码
  9.   // 再次更新副作用队列,在前面的函数式组件更新阶段后,`effct list`这条链表只有子节点,没有挂载到根节点上,默认情况下fiber节点的副作用队列是不包括自身的,如果根节点也存在 `effectTag`,那么就需要把根节点拼接到链表的末尾,形成一条完整的 `effect list`,取出第一个需要处理的fiber节点
  10.   let firstEffect
  11.   if (finishedWork.flags > PerformedWork) {
  12.     if (finishedWork.lastEffect !== null) {
  13.       finishedWork.lastEffect.nextEffect = finishedWork
  14.       firstEffect = finishedWork.firstEffect
  15.     } else {
  16.       firstEffect = finishedWork
  17.     }
  18.   } else {
  19.     // There is no effect on the root.
  20.     firstEffect = finishedWork.firstEffect
  21.   }
  22.   // 忽略代码
  23.   if (firstEffect !== null) {
  24.     nextEffect = firstEffect
  25.     // 进入第二阶段
  26.     // beforeMutation 子阶段:
  27.     // 执行 commitBeforeMutationEffects
  28.     // mutation 子阶段:
  29.     // 执行 commitMutationEffects
  30.     // layout 子阶段:
  31.     // 执行 commitLayoutEffects
  32.   }
  33.   // 忽略代码
  34. }
复制代码
2:提交阶段。

这里我们有三个子阶段,对应的是三个函数 commitBeforeMutationEffects,commitMutationEffects,commitLayoutEffects
  1. let firstEffect = finishedWork.firstEffect
  2. if (firstEffect !== null) {
  3.   const prevExecutionContext = executionContext
  4.   executionContext |= CommitContext
  5.   // 阶段1: dom改变之前
  6.   nextEffect = firstEffect
  7.   do {
  8.     commitBeforeMutationEffects()
  9.   } while (nextEffect !== null)
  10.   // 阶段2: dom改变, 界面发生改变
  11.   nextEffect = firstEffect
  12.   do {
  13.     commitMutationEffects(root, renderPriorityLevel)
  14.   } while (nextEffect !== null)
  15.   // 恢复界面状态
  16.   resetAfterCommit(root.containerInfo)
  17.   // 切换current指针
  18.   root.current = finishedWork
  19.   // 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等
  20.   nextEffect = firstEffect
  21.   do {
  22.     commitLayoutEffects(root, lanes)
  23.   } while (nextEffect !== null)
  24.   nextEffect = null
  25.   executionContext = prevExecutionContext
  26. }
复制代码
子阶段1:在dom变化前,commitBeforeMutationEffects会主要做两件事。1、执行getSnapshowBeforeUpdate(commitBeforeMutationEffectOnFiber)生命周期方法。2、调度useEffect
  1. function commitBeforeMutationEffects() {
  2.   while (nextEffect !== null) {
  3.     // 忽略代码
  4.     const effectTag = nextEffect.effectTag
  5.     if ((effectTag & Snapshot) !== NoEffect) {
  6.       // 忽略代码
  7.       commitBeforeMutationEffectOnFiber(current, nextEffect)
  8.     }
  9.     // 忽略代码
  10.     nextEffect = nextEffect.nextEffect
  11.   }
  12. }
  13. // 省略代码
  14. function commitBeforeMutationEffects() {
  15.   while (nextEffect !== null) {
  16.     const current = nextEffect.alternate
  17.     const flags = nextEffect.flags
  18.     // 处理Snapshot标记
  19.     if ((flags & Snapshot) !== NoFlags) {
  20.       // 这里实际就是处理2个节点,对于ClassComponent类型节点, 调用instance.getSnapshotBeforeUpdate生命周期函数,对于HostRoot类型节点, 调用clearContainer清空了容器节点(即`div#root`这个 dom 节点).
  21.       commitBeforeMutationEffectOnFiber(current, nextEffect)
  22.     }
  23.     // 处理Passive标记
  24.     if ((flags & Passive) !== NoFlags) {
  25.       // 处理useEffect
  26.       // 用于执行useEffect的回调函数,不立即执行,放在scheduleCallBack的回调中,异步根据优先级执行这个回调函数,如果说存在Passive effect,就将rootDoesHavePassiveEffects置为true,并且加入调度,而我们整个commit是一个同步执行操作,所以useEffect会在commit完成后去异步执行。这就跟上面的对上了。
  27.       if (!rootDoesHavePassiveEffects) {
  28.         rootDoesHavePassiveEffects = true
  29.         scheduleCallback(NormalSchedulerPriority, () => {
  30.           flushPassiveEffects()
  31.           return null
  32.         })
  33.       }
  34.     }
  35.     nextEffect = nextEffect.nextEffect
  36.   }
  37. }
复制代码
子阶段2:dom 变更, 界面得到更新. 处理副作用队列中带有ContentReset, Ref, Placement, Update, Deletion, Hydrating标记的fiber节点. 调用栈:

  • 新增: 函数调用栈 commitPlacement -> insertOrAppendPlacementNode或者insertOrAppendPlacementNode-> appendChild或者insertBefore
为什么会有或那,因为实际上我们的fiber树和dom数并不表现一致,后面单独写一篇文章讲一下。

  • 更新: 函数调用栈 commitWork -> commitUpdate
commitWork,会根据fiber节点的不同做不同的更新操作

  • 删除: 函数调用栈 commitDeletion -> unmountHostComponents -> removeChild
commitDeletion其实是调用unmountHostComponents,对不同的节点类型做销毁操作。
我们最终就会调用react-dom里面的api,去让页面实现更新
  1. function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  2.   while (nextEffect !== null) {
  3.     const effectTag = nextEffect.effectTag;
  4.     // 如果有 ContentReset,会重置文本节点
  5.     if (effectTag & ContentReset) {
  6.       commitResetTextContent(nextEffect);
  7.     }
  8.     // 如果有 Ref,会执行 ref 相关的更新
  9.     if (flags & Ref) {
  10.         const current = nextEffect.alternate;
  11.         if (current !== null) {
  12.           // 先清空ref, 在commitRoot的第三阶段(dom变更后), 再重新赋值
  13.           commitDetachRef(current);
  14.         }
  15.      }
  16.     // 处理dom的变化
  17.     const primaryEffectTag =
  18.       effectTag & (Placement | Update | Deletion | Hydrating);
  19.     switch (primaryEffectTag) {
  20.       // 如果需要插入节点,会执行 commitPlacement
  21.       case Placement: {
  22.         commitPlacement(nextEffect);
  23.         nextEffect.effectTag &= ~Placement;
  24.         break;
  25.       }
  26.       // 如果需要更新节点,会执行 commitWork
  27.       case Update: {
  28.         const current = nextEffect.alternate;
  29.         commitWork(current, nextEffect);
  30.         break;
  31.       }
  32.       // 如果需要删除节点,会执行 commitDeletion
  33.       case Deletion: {
  34.         commitDeletion(root, nextEffect, renderPriorityLevel);
  35.         break;
  36.       }
  37.       // 忽略代码
  38.     }
  39.     // 取出下一个 fiber 节点,进入下一次循环
  40.     nextEffect = nextEffect.nextEffect;
  41.   }
  42. }
复制代码
子阶段3:layout,commitLayoutEffects核心是执行commitLayoutEffectOnFiber这个阶段会根据fiber节点的类型执行不同的处理。
  1. function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
  2.   // 忽略代码
  3.   while (nextEffect !== null) {
  4.     const flags = nextEffect.flags;
  5.     // 处理标记
  6.     if (flags & (Update | Callback)) {
  7.       const current = nextEffect.alternate;
  8.       commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
  9.     }
  10.     if (flags & Ref) {
  11.       // 重新设置ref
  12.       commitAttachRef(nextEffect);
  13.     }
  14.     // 忽略代码
  15.     nextEffect = nextEffect.nextEffect;
  16.   }
  17.   // 忽略代码
  18. }
复制代码
commitLayoutEffectOnFiber是引入commitLifeCycles设置的别名,我们先针对于 FunctionComponent这个case来分析后面的调用逻辑(因为函数式组件是现在的主流嘛),这里会把 HookLayout 这个 tag 类型传给 commitHookEffectListMount 方法,也就是说接下来的commitHookEffectListMount会执行 useLayoutEffect 的回调函数。接着执行schedulePassiveEffects,就是把带有Passive标记的effect筛选出来(由useEffect创建), 添加到一个全局数组(pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount).。
  1. function commitLifeCycles(
  2.   finishedRoot: FiberRoot,
  3.   current: Fiber | null,
  4.   finishedWork: Fiber,
  5.   committedLanes: Lanes,
  6. ): void {
  7.   switch (finishedWork.tag) {
  8.     case FunctionComponent:
  9.     case ForwardRef:
  10.     case SimpleMemoComponent:
  11.     case Block: {
  12.         {
  13.         try {
  14.           startLayoutEffectTimer();
  15.           // 执行useLayoutEffect回调函数
  16.           commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
  17.         } finally {
  18.           recordLayoutEffectDuration(finishedWork);
  19.         }
  20.       }
  21.       // finishedWork指的是正在被遍历的有副作用的fiber,放入调度中
  22.       schedulePassiveEffects(finishedWork);
  23.       return;
  24.     }
  25. }
复制代码
对于FunctionComponent,commitHookEffectListMount方法会执行我们的effect.creat并指向destory销毁,接着执行schedulePassiveEffects方法,在这里会分别注册 useEffect ,推进 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount 这两个数组中,用于后续flushPassveEffects执行。
  1. function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
  2.   const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  3.   const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  4.   if (lastEffect !== null) {
  5.     const firstEffect = lastEffect.next;
  6.     let effect = firstEffect;
  7.     do {
  8.       if ((effect.tag & tag) === tag) {
  9.         const create = effect.create;
  10.         //执行effect回调,调用effect.create()之后, 将返回值赋值到effect.destroy.
  11.         effect.destroy = create();
  12.       }
  13.       effect = effect.next;
  14.     } while (effect !== firstEffect);
  15.   }
  16. }
  17. function schedulePassiveEffects(finishedWork: Fiber) {
  18.   // 1. 获取 fiber.updateQueue
  19.   const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  20.   // 2. 获取 effect环形队列
  21.   const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  22.   if (lastEffect !== null) {
  23.     const firstEffect = lastEffect.next;
  24.     let effect = firstEffect;
  25.     do {
  26.       const { next, tag } = effect;
  27.       //  筛选出由useEffect()创建的effect
  28.       if (
  29.         (tag & HookPassive) !== NoHookEffect &&
  30.         (tag & HookHasEffect) !== NoHookEffect
  31.       ) {
  32.         // 把effect添加到全局数组, 等待flushPassiveEffectsImpl处理
  33.         enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
  34.         enqueuePendingPassiveHookEffectMount(finishedWork, effect);
  35.       }
  36.       effect = next;
  37.     } while (effect !== firstEffect);
  38.   }
  39. }
  40. export function enqueuePendingPassiveHookEffectUnmount(
  41.   fiber: Fiber,
  42.   effect: HookEffect,
  43. ): void {
  44.   // unmount effects 数组
  45.   pendingPassiveHookEffectsUnmount.push(effect, fiber);
  46. }
  47. export function enqueuePendingPassiveHookEffectMount(
  48.   fiber: Fiber,
  49.   effect: HookEffect,
  50. ): void {
  51.   // mount effects 数组
  52.   pendingPassiveHookEffectsMount.push(effect, fiber);
  53. }
复制代码
到此时第三子阶段就可以告于段落了,紧接着就是commit的第三个阶段
3:渲染完成后

渲染后主要是做一些清理、检测更新操作,当然我们这里还是以函数式组件为例。
清理:清理有两个地方,一个是链表拆解的清理,因为gc没法回收,就得手动把链表拆开。第二个地方,因为我们前面保存了2个数组unmount effect和mount effects,useEffect会留置到 flushPassiveEffects()监测更新后再去清理。
  1. nextEffect = firstEffect
  2. while (nextEffect !== null) {
  3.   const nextNextEffect = nextEffect.nextEffect
  4.   nextEffect.nextEffect = null
  5.   if (nextEffect.flags & Deletion) {
  6.     detachFiberAfterEffects(nextEffect)
  7.   }
  8.   nextEffect = nextNextEffect
  9. }
复制代码
监测更新:重点是在这个地方,在渲染后的更新我们只说2个必定会调用的监测函数ensureRootIsScheduled和flushSyncCallbackQueue,ensureRootIsScheduled这是用来处理异步任务的(走到我们前文说的流程),flushSyncCallbackQueue处理同步任务,如果有就直接调用,再次进入fiber树构造,
  1. export function flushSyncCallbackQueue() {
  2.   if (immediateQueueCallbackNode !== null) {
  3.     const node = immediateQueueCallbackNode
  4.     immediateQueueCallbackNode = null
  5.     Scheduler_cancelCallback(node)
  6.   }
  7.   flushSyncCallbackQueueImpl()
  8. }
复制代码
到这里就很简单了循坏去执行同步任务,再次构建。
  1. function flushSyncCallbackQueueImpl() {
  2.   if (!isFlushingSyncQueue && syncQueue !== null) {
  3.     // Prevent re-entrancy.
  4.     isFlushingSyncQueue = true
  5.     let i = 0
  6.     if (decoupleUpdatePriorityFromScheduler) {
  7.       const previousLanePriority = getCurrentUpdateLanePriority()
  8.       try {
  9.         const isSync = true
  10.         const queue = syncQueue
  11.         setCurrentUpdateLanePriority(SyncLanePriority)
  12.         runWithPriority(ImmediatePriority, () => {
  13.           for (; i < queue.length; i++) {
  14.             let callback = queue[i]
  15.             do {
  16.               //循坏执行同步任务
  17.               callback = callback(isSync)
  18.             } while (callback !== null)
  19.           }
  20.         })
  21.         syncQueue = null
  22.       } catch (error) {
  23.         // If something throws, leave the remaining callbacks on the queue.
  24.         if (syncQueue !== null) {
  25.           syncQueue = syncQueue.slice(i + 1)
  26.         }
  27.         // Resume flushing in the next tick
  28.         Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue)
  29.         throw error
  30.       } finally {
  31.         setCurrentUpdateLanePriority(previousLanePriority)
  32.         isFlushingSyncQueue = false
  33.       }
  34.     } else {
  35.       try {
  36.         const isSync = true
  37.         const queue = syncQueue
  38.         runWithPriority(ImmediatePriority, () => {
  39.           for (; i < queue.length; i++) {
  40.             let callback = queue[i]
  41.             do {
  42.               callback = callback(isSync)
  43.             } while (callback !== null)
  44.           }
  45.         })
  46.         syncQueue = null
  47.       } catch (error) {
  48.         // If something throws, leave the remaining callbacks on the queue.
  49.         if (syncQueue !== null) {
  50.           syncQueue = syncQueue.slice(i + 1)
  51.         }
  52.         // Resume flushing in the next tick
  53.         Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue)
  54.         throw error
  55.       } finally {
  56.         isFlushingSyncQueue = false
  57.       }
  58.     }
  59.   }
  60. }
复制代码
1.3 ensureRootIsScheduled

注册调度就非常的简单了
  1. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  2.   //判断是否注册新的调度
  3.   const existingCallbackNode = root.callbackNode;
  4.   const nextLanes = getNextLanes(
  5.     root,
  6.     root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  7.   );
  8.   const newCallbackPriority = returnNextLanesPriority();
  9.   // Schedule a new callback.
  10.   let newCallbackNode;
  11.   if (newCallbackPriority === SyncLanePriority) {
  12.     // 注册调度走到scheduler去
  13.     newCallbackNode = scheduleSyncCallback(
  14.       performSyncWorkOnRoot.bind(null, root),
  15.     );
  16.   } else if (newCallbackPriority === SyncBatchedLanePriority) {
  17.     // 批处理注册调度走到scheduler去
  18.     newCallbackNode = scheduleCallback(
  19.       ImmediateSchedulerPriority,
  20.       performSyncWorkOnRoot.bind(null, root),
  21.     );
  22.   } else {
  23.     // 并发注册调度走到scheduler去
  24.     const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
  25.       newCallbackPriority,
  26.     );
  27.     newCallbackNode = scheduleCallback(
  28.       schedulerPriorityLevel,
  29.       performConcurrentWorkOnRoot.bind(null, root),
  30.     );
  31.   }
  32.   root.callbackPriority = newCallbackPriority;
  33.   root.callbackNode = newCallbackNode;
  34. }
复制代码
说来说去我们就只关心一个事情newCallbackNode = scheduleCallback( ImmediateSchedulerPriority, performSyncWorkOnRoot.bind(null, root), );他这里把回调函数给到调度中心并绑定节点,等待调度执行之后重复上面1.2的所有操作,我们下一节分析调度是怎么做的。
2.手写
  1. /*
  2. * @Description:
  3. * @Date: 2022-11-23 22:44:29
  4. */
  5. import { arrified, getRoot, getTag, createStateNode } from '../until'
  6. import { commitAllWork } from './commit'
  7. import { scheduleCallback } from '../scheduler'
  8. let first = 1
  9. let subTask = null
  10. let pendingCommit = null
  11. // 构建最外层的fiber对象
  12. function createOutFiber(jsx, root) {
  13.   const task = {
  14.     root,
  15.     props: {
  16.       children: jsx
  17.     }
  18.   }
  19.   let outFiber
  20.   if (task.from === 'class_component') {
  21.     const root = getRoot(task.instance)
  22.     task.instance.__fiber.partialState = task.partialState
  23.     outFiber = {
  24.       props: root.props,
  25.       stateNode: root.stateNode,
  26.       tag: 'host_root',
  27.       effects: [],
  28.       child: null,
  29.       alternate: root
  30.     }
  31.     return outFiber
  32.   }
  33.   outFiber = {
  34.     props: task.props,
  35.     stateNode: task.root,
  36.     tag: 'host_root',
  37.     effects: [],
  38.     child: null,
  39.     alternate: task.root.__rootFiberContainer
  40.   }
  41.   return outFiber
  42. }
  43. function reconcileChildren(fiber, children) {
  44.   /**
  45.    * children 可能对象 也可能是数组
  46.    * 将children 转换成数组
  47.    */
  48.   const arrifiedChildren = arrified(children)
  49.   /**
  50.    * 循环 children 使用的索引
  51.    */
  52.   let index = 0
  53.   /**
  54.    * children 数组中元素的个数
  55.    */
  56.   let numberOfElements = arrifiedChildren.length
  57.   /**
  58.    * 循环过程中的循环项 就是子节点的 virtualDOM 对象
  59.    */
  60.   let element = null
  61.   /**
  62.    * 子级 fiber 对象
  63.    */
  64.   let newFiber = null
  65.   /**
  66.    * 上一个兄弟 fiber 对象
  67.    */
  68.   let prevFiber = null
  69.   let alternate = null
  70.   if (fiber.alternate && fiber.alternate.child) {
  71.     alternate = fiber.alternate.child
  72.   }
  73.   console.log(arrifiedChildren)
  74.   while (index < numberOfElements || alternate) {
  75.     /**
  76.      * 子级 virtualDOM 对象
  77.      */
  78.     element = arrifiedChildren[index]
  79.     if (!element && alternate) {
  80.       /**
  81.        * 删除操作
  82.        */
  83.       alternate.effectTag = 'delete'
  84.       fiber.effects.push(alternate)
  85.     } else if (element && alternate) {
  86.       /**
  87.        * 更新
  88.        */
  89.       newFiber = {
  90.         type: element.type,
  91.         props: element.props,
  92.         tag: getTag(element),
  93.         effects: [],
  94.         effectTag: 'update',
  95.         parent: fiber,
  96.         alternate
  97.       }
  98.       if (element.type === alternate.type) {
  99.         /**
  100.          * 类型相同
  101.          */
  102.         newFiber.stateNode = alternate.stateNode
  103.       } else {
  104.         /**
  105.          * 类型不同
  106.          */
  107.         newFiber.stateNode = createStateNode(newFiber)
  108.       }
  109.     } else if (element && !alternate) {
  110.       /**
  111.        * 初始渲染
  112.        */
  113.       /**
  114.        * 子级 fiber 对象
  115.        */
  116.       newFiber = {
  117.         type: element.type,
  118.         props: element.props,
  119.         tag: getTag(element),
  120.         effects: [],
  121.         effectTag: 'placement',
  122.         parent: fiber
  123.       }
  124.       /**
  125.        * 为fiber节点添加DOM对象或组件实例对象
  126.        */
  127.       newFiber.stateNode = createStateNode(newFiber)
  128.       newFiber.stateNode = createStateNode(newFiber)
  129.     }
  130.     if (index === 0) {
  131.       fiber.child = newFiber
  132.     } else if (element) {
  133.       prevFiber.sibling = newFiber
  134.     }
  135.     if (alternate && alternate.sibling) {
  136.       alternate = alternate.sibling
  137.     } else {
  138.       alternate = null
  139.     }
  140.     // 更新
  141.     prevFiber = newFiber
  142.     index++ //保存构建fiber节点的索引,等待事件后通过索引再次进行构建
  143.   }
  144. }
  145. function workLoopSync() {
  146.   while (subTask) {
  147.     subTask = performUnitOfWork(subTask)
  148.   }
  149.   if (pendingCommit) {
  150.     first++
  151.     commitAllWork(pendingCommit)
  152.   }
  153. }
  154. // 构建子集的fiber对象的任务单元
  155. function performUnitOfWork(fiber) {
  156.   reconcileChildren(fiber, fiber.props.children)
  157.   /**
  158.    * 如果子级存在 返回子级
  159.    * 将这个子级当做父级 构建这个父级下的子级
  160.    */
  161.   if (fiber.child) {
  162.     return fiber.child
  163.   }
  164.   /**
  165.    * 如果存在同级 返回同级 构建同级的子级
  166.    * 如果同级不存在 返回到父级 看父级是否有同级
  167.    */
  168.   let currentExecutelyFiber = fiber
  169.   while (currentExecutelyFiber.parent) {
  170.     currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(
  171.       currentExecutelyFiber.effects.concat([currentExecutelyFiber])
  172.     )
  173.     if (currentExecutelyFiber.sibling) {
  174.       return currentExecutelyFiber.sibling
  175.     }
  176.     currentExecutelyFiber = currentExecutelyFiber.parent
  177.   }
  178.   pendingCommit = currentExecutelyFiber
  179.   console.log(pendingCommit)
  180. }
  181. // 源码地址 https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L674-L736
  182. function ensureRootIsScheduled(fiber) {
  183.   //这里我们可以直接走到注册调度任务,暂时我们分析的是Legacy模式,Concurrent模式实现的performConcurrentWorkOnRoot实现的可中断渲染可以以后实现
  184.   let newCallbackNode
  185.   //接下来就可以直接走到调度中心去
  186.   newCallbackNode = scheduleCallback(workLoopSync)
  187. }
  188. // 源码地址 https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619
  189. function scheduleUpdateOnFiber(fiber) {
  190.   subTask = fiber
  191.   if (!first) {
  192.     //对应暂无render上下文
  193.     // 对于初次构建来说我们直接进行`fiber构造`.
  194.     workLoopSync()
  195.   } else {
  196.     //对于后续更新以及操作都选择去注册调度任务
  197.     ensureRootIsScheduled(subTask)
  198.   }
  199. }
  200. export function render(jsx, root) {
  201.   const outFiber = createOutFiber(jsx, root)
  202.   scheduleUpdateOnFiber(outFiber)
  203. }
复制代码
然后我们就快乐的简单实现了fiberDom的过程~~~
总结

比较多工程化和基础的东西,还没完善,有兴趣的可以gitbub拉下来试试

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册