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

news2025/6/7 17:50:20

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

好的,我明白了。您提供的文本主要介绍了 React 源码中 Fiber 核心的构建原理,涵盖了从执行上下文到构建、提交、调度等关键阶段,以及相关的代码实现。

您提出的关联问题也很重要,它们深入探讨了 Fiber 的优化、代码的扩展性和调度的细节理解。

这些都是理解 React Fiber 工作机制的关键点。如果您想进一步探讨这些问题,例如:

  • Fiber 在哪些方面进行了优化,以及具体的优化手段是什么?

  • 手写 Fiber 实现可以如何扩展,用于不同的渲染目标或特性?

  • React 的调度(Scheduler)是如何工作的,其细节如何帮助提升用户体验?

欢迎随时提出,我很乐意和您一起探讨。

react-reconciler核心逻辑理解并手写

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

1.react构建fiber核心代码

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

1.1 入口

在全局变量中有executionContext, 代表渲染期间执行上下文, 它也是一个二进制表示的变量, 通过位运算进行操作. 在源码中一共定义了 8 种执行栈:

type ExecutionContext = number;
export const NoContext = /*             */ 0b0000000;
const BatchedContext = /*               */ 0b0000001;
const EventContext = /*                 */ 0b0000010;
const DiscreteEventContext = /*         */ 0b0000100;
const LegacyUnbatchedContext = /*       */ 0b0001000;
const RenderContext = /*                */ 0b0010000;
const CommitContext = /*                */ 0b0100000;

这里我们只分析Legacy模式(对启动模式不清楚的可以看看[开篇] Legacy模式下的首次更新, 会直接进行构建,然后经过提交到页面上,并不会进入去注册调度任务 接下来从输入[阅读须知]开始我们看核心的输入源码

export function scheduleUpdateOnFiber(
fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  if (lane === SyncLane) {
    // legacy模式
    if (
      // 首次无上下文也无渲染上下文
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // 初次更新
      performSyncWorkOnRoot(root);
    } else {
      // 后续的更新,注册调度任务
      ensureRootIsScheduled(root, eventTime);
    }
  }
}

在 render 过程中, 每一个阶段都会改变executionContext(render 之前, 会设置executionContext |= RenderContext; commit 之前, 会设置executionContext |= CommitContext, 假设在render过程中再次发起更新(如在UNSAFE_componentWillReceiveProps生命周期中调用setState)则可通过executionContext来判断当前的render状态。

上面我们已经看到了入口的函数scheduleUpdateOnFiber,接下来我们分析入口函数中的2个关键的函数performSyncWorkOnRootensureRootIsScheduled

1.2 performSyncWorkOnRoot

首先我们来分析performSyncWorkOnRoot,里面的主要做的事情就是做fiber树的构建以及最后的提交commitRoot。在这个函数中我们也可以分为2个阶段,一个阶段是fiber树的构建,另一个阶段是commitRoot的提交(输出)。

阶段1.fiber树的构建
 
function performSyncWorkOnRoot(root) {
  let lanes;
  let exitStatus;
  // workInProgressRoot当前构建的任务
  if (
    root === workInProgressRoot &&
    includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
  ) {
    // 初次构建的时候从root节点开始, 形成一个完成的fiberdom树
    lanes = workInProgressRootRenderLanes;
    exitStatus = renderRootSync(root, lanes);
  } else {
    // 1. 获取本次render的优先级, 初次构造返回 NoLanes
    lanes = getNextLanes(root, NoLanes);
    // 2. 从root节点开始, 至上而下更新,形成一个完成的fiberdom树
    exitStatus = renderRootSync(root, lanes);
  }

  // 将最新的fiber树挂载到root.finishedWork节点上
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  // 进入commit阶段,渲染到页面上
  commitRoot(root);
}

实际我们看到不管是初次构建还是后续更新都会走到`renderRootSync`上去构建fiber树,那它又做了什么那


function renderRootSync(root: FiberRoot, lanes: Lanes) {
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;
  // 如果fiberRoot变动, 或者update.lane变动,update(链表我们后面讲)
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    // 刷新栈帧
    prepareFreshStack(root, lanes);
  }
  do {
    try {
      // 核心代码循环构建fiber
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  executionContext = prevExecutionContext;
  // 重置全局变量, 表明render结束
  workInProgressRoot = null;
  workInProgressRootRenderLanes = NoLanes;
  return workInProgressRootExitStatus;
}

如果是初读源码我们可以只关心一个地方那就是workLoopSync,当然这是在legacy模式下,concurrent模式下走的是workLoopConcurrent去实现时间分片和循环可中断。下一步我们就可以看workLoopSync

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress)
  }
}

这里就相当于把循环构建fiberDom树了,而前面备注中也写到了workInProgress指向当前构建的任务或者说节点更适合一些,来到构建fiberDom树的performUnitOfWork.

function performUnitOfWork(unitOfWork: Fiber): void {
  // 指向当前页面的`fiber`节点. 初次构造时, 页面还未渲染, 就是空的
  const current = unitOfWork.alternate;
  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    //构建出最终的fiberDom,并且返回dom结构的chilrden处理之后的child
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // 没新节点的时候就可以准备回溯洛
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  ReactCurrentOwner.current = null;
}

进入beginWork这里主要做的事情是通过ReactElement去完成fiber树的更新和构建,他的细节是在在于针对每一个fiber类型分别有不同的update函数去做处理,值得一提的是这里基本完成了fiber本身基础状态的一些设置,我觉得我们这个阅读阶段我们先关注主流程的事情,也就是updateXXX的处理。

function beginWork(
current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // 忽略代码
  // 这里是对于所有fiber类型的不同处理,我们关心不同tag怎么处理的就行了,等下手写的时候写简单一点
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateLanes,
        renderLanes,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
   //忽略代码
  }
}

举个例子updateHostRoot,这里我就可以看到一些具体的fiber构建的细节了。

function updateHostRoot(current, workInProgress, renderLanes) {
  // 1. 状态计算, 更新整合到 workInProgress.memoizedState中来
  const updateQueue = workInProgress.updateQueue;
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  const prevChildren = prevState !== null ? prevState.element : null;
  cloneUpdateQueue(current, workInProgress);
  // 遍历updateQueue.shared.pending, 提取有足够优先级的update对象, 计算出最终的状态 workInProgress.memoizedState
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  const nextState = workInProgress.memoizedState;
  // 2. 获取下级`ReactElement`对象
  const nextChildren = nextState.element;
  const root: FiberRoot = workInProgress.stateNode;
  if (root.hydrate && enterHydrationState(workInProgress)) {
    // ...服务端渲染相关, 此处省略
  } else {
    // 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }
  // 返回child进行下一次循坏
  return workInProgress.child;
}

而在此之后那我们就进去一个由下往上的回溯阶段(递归中的归),也就是completeUnitOfWorkperformUnitOfWork中的方法),同样我们关注在当前阅读这个主流程上函数做了什么,首先他给stateNode去指向了一个Dom实例,设置属性绑定事件,设置flag标记,紧接着处理副作用链表队列[就是更新头尾指针],emm有数据结构算法基础的可以看看),然后有一些兄弟节点子节点的判断。

function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  // 外层循环控制并移动指针(`workInProgress`,`completedWork`等)
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    if ((completedWork.flags & Incomplete) === NoFlags) {
      let next;
      // 1. 处理Fiber节点, 关联Fiber节点和dom对象, 绑定事件
      next = completeWork(current, completedWork, subtreeRenderLanes); // 处理单个节点
      if (next !== null) {
        // 子节点, 则回到beginWork阶段进行处理
        workInProgress = next;
        return;
      }
      // 重置子节点的优先级
      resetChildLanes(completedWork);
      if (
        returnFiber !== null &&
        (returnFiber.flags & Incomplete) === NoFlags
      ) {
        // 2. 收集当前Fiber节点以及其子树的副作用effects
        // 2.1 把子节点的副作用队列添加到父节点上,只有父节点的firstEffect不存在时
        // 才能将父节点的firstEffect指向当前节点的副作用单向链表头
        if (returnFiber.firstEffect === null) {
          //让父节点的firstEffect指向当前节点的firstEffect
          returnFiber.firstEffect = completedWork.firstEffect;
        }
        //当前节点不存在副作用链表才加
        if (completedWork.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
          }
          // 将当前节点加到副作用链表中
          returnFiber.lastEffect = completedWork.lastEffect;
        }
        // 2.2 如果当前fiber节点有副作用, 将其添加到子节点的副作用队列之后.
        const flags = completedWork.flags;
        if (flags > PerformedWork) {
          // 忽略PerformedWork
          // 将当前节点添加到副作用链表尾部
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          } else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
      }
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // 如果有兄弟节点, 返回之后再次进入beginWork阶段
      workInProgress = siblingFiber;
      return;
    }
    // 移动指针, 指向下一个节点
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
  // 已回溯到根节点, 设置workInProgressRootExitStatus = RootCompleted
  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

到这那我们已经就可以走到上面的commitRoot将内存里的fiber树输出到react-dom了。

阶段2.fiber树的输出

在我们的构建阶段结束后,会在performSyncWorkOnRootfinishConcurrentRender中的fiberRoot传给commitRoot,去开始commit提交阶段,此时已经到了fiber的最终输出阶段,经过了一系列优先级转换,最终会执行commitRootImpl


function commitRoot(root) {
  // 渲染等级
  const renderPriorityLevel = getCurrentPriorityLevel()
  // 调度等级
  runWithPriority(ImmediateSchedulerPriority, commitRootImpl.bind(null, root, renderPriorityLevel))
  return null
}

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,开始三个子阶段。

function commitRootImpl(root, renderPriorityLevel) {
  // 准备提交阶段
  // 判断rootWithPendingPassiveEffects,就是说先判断是否有未执行的useEffect,如果有先执行,等待我们上一轮的useEffect执行完后才继续我们commit。
  do {
    //
    flushPassiveEffects()
  } while (rootWithPendingPassiveEffects !== null)
  // 忽略代码
  // 再次更新副作用队列,在前面的函数式组件更新阶段后,`effct list`这条链表只有子节点,没有挂载到根节点上,默认情况下fiber节点的副作用队列是不包括自身的,如果根节点也存在 `effectTag`,那么就需要把根节点拼接到链表的末尾,形成一条完整的 `effect list`,取出第一个需要处理的fiber节点
  let firstEffect
  if (finishedWork.flags > PerformedWork) {
    if (finishedWork.lastEffect !== null) {
      finishedWork.lastEffect.nextEffect = finishedWork
      firstEffect = finishedWork.firstEffect
    } else {
      firstEffect = finishedWork
    }
  } else {
    // There is no effect on the root.
    firstEffect = finishedWork.firstEffect
  }
  // 忽略代码
  if (firstEffect !== null) {
    nextEffect = firstEffect
    // 进入第二阶段
    // beforeMutation 子阶段:
    // 执行 commitBeforeMutationEffects

    // mutation 子阶段:
    // 执行 commitMutationEffects

    // layout 子阶段:
    // 执行 commitLayoutEffects
  }
  // 忽略代码
}
2:提交阶段。

这里我们有三个子阶段,对应的是三个函数

commitBeforeMutationEffects,

commitMutationEffects,

commitLayoutEffects

 

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)
  // 恢复界面状态
  resetAfterCommit(root.containerInfo)
  // 切换current指针
  root.current = finishedWork

  // 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等
  nextEffect = firstEffect
  do {
    commitLayoutEffects(root, lanes)
  } while (nextEffect !== null)
  nextEffect = null
  executionContext = prevExecutionContext
}

子阶段1:在dom变化前,commitBeforeMutationEffects会主要做两件事。1、执行getSnapshowBeforeUpdatecommitBeforeMutationEffectOnFiber)生命周期方法。2、调度useEffect

function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
  // 忽略代码

    const effectTag = nextEffect.effectTag
    if ((effectTag & Snapshot) !== NoEffect) {
      // 忽略代码
      commitBeforeMutationEffectOnFiber(current, nextEffect)
    }

    // 忽略代码
    nextEffect = nextEffect.nextEffect
  }
}

// 省略代码
function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    const current = nextEffect.alternate
    const flags = nextEffect.flags
    // 处理Snapshot标记
    if ((flags & Snapshot) !== NoFlags) {
      // 这里实际就是处理2个节点,对于ClassComponent类型节点, 调用instance.getSnapshotBeforeUpdate生命周期函数,对于HostRoot类型节点, 调用clearContainer清空了容器节点(即`div#root`这个 dom 节点).
      commitBeforeMutationEffectOnFiber(current, nextEffect)
    }
    // 处理Passive标记
    if ((flags & Passive) !== NoFlags) {
      // 处理useEffect
      // 用于执行useEffect的回调函数,不立即执行,放在scheduleCallBack的回调中,异步根据优先级执行这个回调函数,如果说存在Passive effect,就将rootDoesHavePassiveEffects置为true,并且加入调度,而我们整个commit是一个同步执行操作,所以useEffect会在commit完成后去异步执行。这就跟上面的对上了。
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true
        scheduleCallback(NormalSchedulerPriority, () => {
          flushPassiveEffects()
          return null
        })
      }
    }
    nextEffect = nextEffect.nextEffect
  }
}

子阶段2:dom 变更, 界面得到更新. 处理副作用队列中带有ContentReset, Ref, Placement, Update, Deletion, Hydrating标记的fiber节点. 调用栈:

1. 新增: 函数调用栈 commitPlacement -> insertOrAppendPlacementNode或者insertOrAppendPlacementNode-> appendChild或者insertBefore

为什么会有或那,因为实际上我们的fiber树和dom数并不表现一致,后面单独写一篇文章讲一下。

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

3. 删除: 函数调用栈 commitDeletion -> unmountHostComponents -> removeChild commitDeletion其实是调用unmountHostComponents

对不同的节点类型做销毁操作。 我们最终就会调用react-dom里面的api,去让页面实现更新

 
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;

    // 如果有 ContentReset,会重置文本节点
    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    // 如果有 Ref,会执行 ref 相关的更新
    if (flags & Ref) {
        const current = nextEffect.alternate;
        if (current !== null) {
          // 先清空ref, 在commitRoot的第三阶段(dom变更后), 再重新赋值
          commitDetachRef(current);
        }
     }
    // 处理dom的变化
    const primaryEffectTag =
      effectTag & (Placement | Update | Deletion | Hydrating);
    switch (primaryEffectTag) {
      // 如果需要插入节点,会执行 commitPlacement
      case Placement: {
        commitPlacement(nextEffect);
        nextEffect.effectTag &= ~Placement;
        break;
      }
      // 如果需要更新节点,会执行 commitWork
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // 如果需要删除节点,会执行 commitDeletion
      case Deletion: {
        commitDeletion(root, nextEffect, renderPriorityLevel);
        break;
      }
      // 忽略代码
    }

    // 取出下一个 fiber 节点,进入下一次循环
    nextEffect = nextEffect.nextEffect;
  }
}

子阶段3:layoutcommitLayoutEffects核心是执行commitLayoutEffectOnFiber这个阶段会根据fiber节点的类型执行不同的处理。

 
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
  // 忽略代码
  while (nextEffect !== null) {

    const flags = nextEffect.flags;
    // 处理标记
    if (flags & (Update | Callback)) {
      const current = nextEffect.alternate;
      commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
    }

    if (flags & Ref) {
      // 重新设置ref
      commitAttachRef(nextEffect);
    }

    // 忽略代码
    nextEffect = nextEffect.nextEffect;
  }

  // 忽略代码
}

commitLayoutEffectOnFiber是引入commitLifeCycles设置的别名,我们先针对于 FunctionComponent这个case来分析后面的调用逻辑(因为函数式组件是现在的主流嘛),这里会把 HookLayout

这个 tag`类型传给 commitHookEffectListMount 方法,也就是说接下来的commitHookEffectListMount会执行 useLayoutEffect 的回调函数。

接着执行schedulePassiveEffects,就是把带有Passive标记的effect筛选出来(由useEffect创建), 添加到一个全局数组(pendingPassiveHookEffectsUnmountpendingPassiveHookEffectsMount).。

function commitLifeCycles(
 finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedLanes: Lanes,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block: {
        {
        try {
          startLayoutEffectTimer();
          // 执行useLayoutEffect回调函数
          commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
        } finally {
          recordLayoutEffectDuration(finishedWork);
        }
      }
      // finishedWork指的是正在被遍历的有副作用的fiber,放入调度中
      schedulePassiveEffects(finishedWork);
      return;
    }
}

对于FunctionComponent,commitHookEffectListMount方法会执行我们的effect.creat并指向destory销毁,

接着执行schedulePassiveEffects方法,在这里会分别注册 useEffect ,

推进 pendingPassiveHookEffectsUnmountpendingPassiveHookEffectsMount 这两个数组中,用于后续flushPassveEffects执行。

function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag & tag) === tag) {
        const create = effect.create;
        //执行effect回调,调用effect.create()之后, 将返回值赋值到effect.destroy.
        effect.destroy = create();
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}
function schedulePassiveEffects(finishedWork: Fiber) {
  // 1. 获取 fiber.updateQueue
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  // 2. 获取 effect环形队列
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      const { next, tag } = effect;
      //  筛选出由useEffect()创建的effect
      if (
        (tag & HookPassive) !== NoHookEffect &&
        (tag & HookHasEffect) !== NoHookEffect
      ) {
        // 把effect添加到全局数组, 等待flushPassiveEffectsImpl处理
        enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
        enqueuePendingPassiveHookEffectMount(finishedWork, effect);
      }
      effect = next;
    } while (effect !== firstEffect);
  }
}

export function enqueuePendingPassiveHookEffectUnmount(
  fiber: Fiber,
  effect: HookEffect,
): void {
  // unmount effects 数组
  pendingPassiveHookEffectsUnmount.push(effect, fiber);
}

export function enqueuePendingPassiveHookEffectMount(
  fiber: Fiber,
  effect: HookEffect,
): void {
  // mount effects 数组
  pendingPassiveHookEffectsMount.push(effect, fiber);
}

到此时第三子阶段就可以告于段落了,紧接着就是`commit`的第三个阶段

3:渲染完成后

渲染后主要是做一些清理检测更新操作,当然我们这里还是以函数式组件为例。 清理:清理有两个地方,一个是链表拆解的清理,因为gc没法回收,就得手动把链表拆开。

第二个地方,因为我们前面保存了2个数组unmount effectmount effects,useEffect会留置到 flushPassiveEffects()监测更新后再去清理。

 
nextEffect = firstEffect
while (nextEffect !== null) {
  const nextNextEffect = nextEffect.nextEffect
  nextEffect.nextEffect = null
  if (nextEffect.flags & Deletion) {
    detachFiberAfterEffects(nextEffect)
  }
  nextEffect = nextNextEffect
}

监测更新:重点是在这个地方,在渲染后的更新我们只说2个必定会调用的监测函数ensureRootIsScheduledflushSyncCallbackQueue,ensureRootIsScheduled这是用来处理异步任务的(走到我们前文说的流程),

flushSyncCallbackQueue处理同步任务,如果有就直接调用,再次进入fiber树构造,

function flushSyncCallbackQueueImpl() {
  if (!isFlushingSyncQueue && syncQueue !== null) {
   // Prevent re-entrancy.
    isFlushingSyncQueue = true
    let i = 0
    if (decoupleUpdatePriorityFromScheduler) {
      const previousLanePriority = getCurrentUpdateLanePriority()
      try {
        const isSync = true
        const queue = syncQueue
        setCurrentUpdateLanePriority(SyncLanePriority)
        runWithPriority(ImmediatePriority, () => {
          for (; i < queue.length; i++) {
            let callback = queue[i]
            do {
              //循坏执行同步任务
              callback = callback(isSync)
            } while (callback !== null)
          }
        })
        syncQueue = null
      } catch (error) {
        // If something throws, leave the remaining callbacks on the queue.
        if (syncQueue !== null) {
          syncQueue = syncQueue.slice(i + 1)
        }
        // Resume flushing in the next tick
        Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue)
        throw error
      } finally {
        setCurrentUpdateLanePriority(previousLanePriority)
        isFlushingSyncQueue = false
      }
    } else {
      try {
        const isSync = true
        const queue = syncQueue
        runWithPriority(ImmediatePriority, () => {
          for (; i < queue.length; i++) {
            let callback = queue[i]
            do {
              callback = callback(isSync)
            } while (callback !== null)
          }
        })
        syncQueue = null
      } catch (error) {
        // If something throws, leave the remaining callbacks on the queue.
        if (syncQueue !== null) {
          syncQueue = syncQueue.slice(i + 1)
        }
        // Resume flushing in the next tick
        Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue)
        throw error
      } finally {
        isFlushingSyncQueue = false
      }
    }
  }
}

到这里就很简单了循环去执行同步任务,再次构建。

1.3 ensureRootIsScheduled

注册调度就非常的简单了

IsScheduled(root: FiberRoot, currentTime: number) {
  //判断是否注册新的调度
  const existingCallbackNode = root.callbackNode;
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  const newCallbackPriority = returnNextLanesPriority();
  // Schedule a new callback.
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    // 注册调度走到scheduler去
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    // 批处理注册调度走到scheduler去
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    // 并发注册调度走到scheduler去
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

说来说去我们就只关心一个事情newCallbackNode = scheduleCallback( ImmediateSchedulerPriority, performSyncWorkOnRoot.bind(null, root), );他这里把回调函数给到调度中心并绑定节点,等待调度执行之后重复上面1.2的所有操作,我们下一节分析调度是怎么做的。

2.手写


import { arrified, getRoot, getTag, createStateNode } from '../until'
import { commitAllWork } from './commit'
import { scheduleCallback } from '../scheduler'
let first = 1
let subTask = null
let pendingCommit = null
// 构建最外层的fiber对象
function createOutFiber(jsx, root) {
  const task = {
    root,
    props: {
      children: jsx
    }
  }
  let outFiber
  if (task.from === 'class_component') {
    const root = getRoot(task.instance)
    task.instance.__fiber.partialState = task.partialState
    outFiber = {
      props: root.props,
      stateNode: root.stateNode,
      tag: 'host_root',
      effects: [],
      child: null,
      alternate: root
    }
    return outFiber
  }
  outFiber = {
    props: task.props,
    stateNode: task.root,
    tag: 'host_root',
    effects: [],
    child: null,
    alternate: task.root.__rootFiberContainer
  }
  return outFiber
}

function reconcileChildren(fiber, children) {
  /**
children 可能对象 也可能是数组
将children 转换成数组
   */
  const arrifiedChildren = arrified(children)
  /**
循环 children 使用的索引
   */
  let index = 0
  /**
children 数组中元素的个数
   */
  let numberOfElements = arrifiedChildren.length
  /**
循环过程中的循环项 就是子节点的 virtualDOM 对象
   */
  let element = null
  /**
子级 fiber 对象
   */
  let newFiber = null
  /**
上一个兄弟 fiber 对象
   */
  let prevFiber = null

  let alternate = null
  if (fiber.alternate && fiber.alternate.child) {
    alternate = fiber.alternate.child
  }
  console.log(arrifiedChildren)
  while (index < numberOfElements || alternate) {
    /**
     * 子级 virtualDOM 对象
     */
    element = arrifiedChildren[index]

    if (!element && alternate) {
      /**
       * 删除操作
       */
      alternate.effectTag = 'delete'
      fiber.effects.push(alternate)
    } else if (element && alternate) {
      /**
       * 更新
       */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: 'update',
        parent: fiber,
        alternate
      }
      if (element.type === alternate.type) {
        /**
         * 类型相同
         */
        newFiber.stateNode = alternate.stateNode
      } else {
        /**
         * 类型不同
         */
        newFiber.stateNode = createStateNode(newFiber)
      }
    } else if (element && !alternate) {
      /**
       * 初始渲染
       */
      /**
       * 子级 fiber 对象
       */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: 'placement',
        parent: fiber
      }
      /**
       * 为fiber节点添加DOM对象或组件实例对象
       */
      newFiber.stateNode = createStateNode(newFiber)
      newFiber.stateNode = createStateNode(newFiber)
    }

    if (index === 0) {
      fiber.child = newFiber
    } else if (element) {
      prevFiber.sibling = newFiber
    }

    if (alternate && alternate.sibling) {
      alternate = alternate.sibling
    } else {
      alternate = null
    }

    // 更新
    prevFiber = newFiber
    index++ //保存构建fiber节点的索引,等待事件后通过索引再次进行构建
  }
}

function workLoopSync() {
  while (subTask) {
    subTask = performUnitOfWork(subTask)
  }
  if (pendingCommit) {
    first++
    commitAllWork(pendingCommit)
  }
}

// 构建子集的fiber对象的任务单元
function performUnitOfWork(fiber) {
  reconcileChildren(fiber, fiber.props.children)
  /**
如果子级存在 返回子级
将这个子级当做父级 构建这个父级下的子级
   */
  if (fiber.child) {
    return fiber.child
  }

  /**
如果存在同级 返回同级 构建同级的子级
如果同级不存在 返回到父级 看父级是否有同级
   */
  let currentExecutelyFiber = fiber
  while (currentExecutelyFiber.parent) {
    currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(
      currentExecutelyFiber.effects.concat([currentExecutelyFiber])
    )

    if (currentExecutelyFiber.sibling) {
      return currentExecutelyFiber.sibling
    }
    currentExecutelyFiber = currentExecutelyFiber.parent
  }
  pendingCommit = currentExecutelyFiber
  console.log(pendingCommit)
}

// 源码地址 https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L674-L736
function ensureRootIsScheduled(fiber) {
  //这里我们可以直接走到注册调度任务,暂时我们分析的是Legacy模式,Concurrent模式实现的performConcurrentWorkOnRoot实现的可中断渲染可以以后实现
  let newCallbackNode

  //接下来就可以直接走到调度中心去
  newCallbackNode = scheduleCallback(workLoopSync)
}

// 源码地址 https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619
function scheduleUpdateOnFiber(fiber) {
  subTask = fiber
  if (!first) {
    //对应暂无render上下文
    // 对于初次构建来说我们直接进行`fiber构造`.
    workLoopSync()
  } else {
    //对于后续更新以及操作都选择去注册调度任务
    ensureRootIsScheduled(subTask)
  }
}
export function render(jsx, root) {
  const outFiber = createOutFiber(jsx, root)
  scheduleUpdateOnFiber(outFiber)
}

然后我们就快乐的简单实现了fiberDom的过程~~~

# 总结

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

欢迎加入群聊,我们一起讨论一些更有趣的技术、商业、闲聊。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

视频监控管理平台EasyCVR与V4分析网关对接后告警照片的清理优化方案

一、问题概述​ 在安防监控、设备运维等场景中&#xff0c;用户将视频监控管理平台EasyCVR与V4网关通过http推送方式协同工作时&#xff0c;硬件盒子上传的告警图片持续累积&#xff0c;导致EasyCVR服务器存储空间耗尽&#xff0c;影响系统正常运行与告警功能使用。 二、解决方…

机器学习:决策树和剪枝

本文目录&#xff1a; 一、决策树基本知识&#xff08;一&#xff09;概念&#xff08;二&#xff09;决策树建立过程 二、决策树生成&#xff08;一&#xff09;ID3决策树&#xff1a;基于信息增益构建的决策树。&#xff08;二&#xff09;C4.5决策树&#xff08;三&#xff…

vscode自定义主题语法及流程

vscode c/c 主题 DIY 启用自己的主题(最后步骤) 重启生效 手把手教你制作 在C:\Users\jlh.vscode\extensions下自己创建一个文件夹 里面有两个文件一个文件夹 package.json: {"name":"theme-jlh","displayName":"%displayName%&qu…

vue中加载Cesium地图(天地图、高德地图)

目录 1、将下载的Cesium包移动至public下 2、首先需要将Cesium.js和widgets.css文件引入到 3、 新建Cesium.js文件&#xff0c;方便在全局使用 4、新建cesium.vue文件&#xff0c;展示三维地图 1、将下载的Cesium包移动至public下 npm install cesium后​​​​​​​ 2、…

SpringBoot整合RocketMQ与客户端注意事项

SpringBoot整合RocketMQ 引入依赖&#xff08;5.3.0比较稳定&#xff09; <dependencies><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.3.1</version&…

Mysql的卸载与安装

确保卸载干净mysql 不然在进行mysal安装时候会出现不一的页面和问题 1、卸载 在应用页面将查询到的mysql相关应用卸载 2、到c盘下将残留的软件包进行数据删除 3、删除programData下的mysql数据 4、检查系统中的mysql是否存在 cmd中执行 sc deleted mysql80 5、删除注册表中的…

Excel处理控件Aspose.Cells教程:使用 C# 在 Excel 中创建组合图表

可视化项目时间线对于有效规划和跟踪至关重要。在本篇教程中&#xff0c;您将学习如何使用 C# 在 Excel 中创建组合图。只需几行代码&#xff0c;即可自动生成动态、美观的组合图。无论您是在构建项目管理工具还是处理内部报告&#xff0c;本指南都将向您展示如何将任务数据转换…

【多线程初阶】阻塞队列 生产者消费者模型

文章目录 一、阻塞队列二、生产者消费者模型(一)概念(二)生产者消费者的两个重要优势(阻塞队列的运用)1) 解耦合(不一定是两个线程之间,也可以是两个服务器之间)2) 削峰填谷 (三)生产者消费者模型付出的代价 三、标准库中的阻塞队列(一)观察模型的运行效果(二)观察阻塞效果1) 队…

《100天精通Python——基础篇 2025 第5天:巩固核心知识,选择题实战演练基础语法》

目录 一、踏上Python之旅二、Python输入与输出三、变量与基本数据类型四、运算符五、流程控制 一、踏上Python之旅 1.想要输出 I Love Python,应该使用()函数。 A.printf() B.print() C.println() D.Print() 在Python中想要在屏幕中输出内容&#xff0c;应该使用print()函数…

机器人夹爪的选型与ROS通讯——机器人抓取系统基础系列(六)

文章目录 前言一、夹爪的选型1.1 任务需求分析1.2 软体夹爪的选型 二、夹爪的ROS通讯2.1 夹爪的通信方式介绍2.2 串口助手测试2.3 ROS通讯节点实现 总结Reference: 前言 本文将介绍夹爪的选型方法和通讯方式。以鞋子这类操作对象为例&#xff0c;将详细阐述了对应的夹爪选型过…

第二十八章 RTC——实时时钟

第二十八章 RTC——实时时钟​​​​​​​ 目录 第二十八章 RTC——实时时钟 1 RTC实时时钟简介 2 RTC外设框图剖析 3 UNIX时间戳 4 与RTC控制相关的库函数 4.1 等待时钟同步和操作完成 4.2 使能备份域涉及RTC配置 4.3 设置RTC时钟分频 4.4 设置、获取RTC计数器及闹钟 5 实时时…

使用 DuckLake 和 DuckDB 构建 S3 数据湖实战指南

本文介绍了由 DuckDB 和 DuckLake 组成的轻量级数据湖方案&#xff0c;旨在解决传统数据湖&#xff08;如HadoopHive&#xff09;元数据管理复杂、查询性能低及厂商锁定等问题。该方案为中小规模数据湖场景提供了简单、高性能且无厂商锁定的替代选择。 1. 什么是 DuckLake 和 D…

大语言模型提示词(LLM Prompt)工程系统性学习指南:从理论基础到实战应用的完整体系

文章目录 前言&#xff1a;为什么提示词工程成为AI时代的核心技能一、提示词的本质探源&#xff1a;认知科学与逻辑学的理论基础1.1 认知科学视角下的提示词本质信息处理理论的深层机制图式理论的实际应用认知负荷理论的优化策略 1.2 逻辑学框架下的提示词架构形式逻辑的三段论…

如何基于Mihomo Party http端口配置git与bash命令行代理

如何基于Mihomo Party http端口配置git与bash命令行代理 1. 确定Mihomo Party http端口配置 点击内核设置后即可查看 默认7892端口&#xff0c;开启允许局域网连接 2. 配置git代理 配置本机代理可以使用 127.0.0.1 配置局域网内其它机代理需要使用本机的非回环地址 IP&am…

埃文科技智能数据引擎产品入选《中国网络安全细分领域产品名录》

嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》&#xff0c;埃文科技智能数据引擎产品成功入选数据分级分类产品名录。 在数字化转型加速的今天&#xff0c;网络安全已成为企业生存与发展的核心基石&#xff0c;为了解这一蓬勃发展的产业格局&#xff0c;嘶吼安全产业…

NLP学习路线图(二十六):自注意力机制

一、为何需要你&#xff1f;序列建模的困境 在你出现之前&#xff0c;循环神经网络&#xff08;RNN&#xff09;及其变种LSTM、GRU是处理序列数据&#xff08;如文本、语音、时间序列&#xff09;的主流工具。它们按顺序逐个处理输入元素&#xff0c;将历史信息压缩在一个隐藏…

Unity3D仿星露谷物语开发60之定制角色其他部位

1、目标 上一篇中定制了角色的衬衫、手臂。 本篇中将定制角色其他部位的图形&#xff0c;包括&#xff1a;裤子、发型、皮肤、帽子等。 2、定制裤子 &#xff08;1&#xff09;修改ApplyCharacterCustomisation.cs脚本 我们需要设置一个输入框选择裤子的颜色。 // Select …

Google机器学习实践指南(机器学习模型泛化能力)

&#x1f525; Google机器学习(14)-机器学习模型泛化能力解析 Google机器学习(14)-机器学习模型泛化原理与优化&#xff08;约10分钟&#xff09; 一、泛化问题引入 ▲ 模型表现对比&#xff1a; 假设森林中树木健康状况预测模型&#xff1a; 图1&#xff1a;初始模型表现 …

MySQL性能调优:Mysql8高频面试题汇总

1&#xff0c;主键和唯一键有什么区别&#xff1f; 主键不能重复&#xff0c;不能为空&#xff0c;唯一键不能重复&#xff0c;可以为空。 建立主键的目的是让外键来引用。 一个表最多只有一个主键&#xff0c;但可以有很多唯一键 2&#xff0c;MySQL常用的存储引擎有哪些&…

vue+elementUI+springboot实现文件合并前端展示文件类型

项目场景&#xff1a; element的table上传文件并渲染出文件名称点击所属行可以查看文件,并且可以导出合并文件,此文章是记录合并文档前端展示的帖子 解决方案&#xff1a; 后端定义三个工具类 分别是pdf,doc和word的excle的目前我没整 word的工具类 package com.sc.modules…