react源码,过一下挂载的流程

2025-2-19 119 2/19

处理之后的源码的地址  https://github.com/raotaohub/as-you-see-react ,直接拉下来就可以调试了,不用任何配置

调试源码之前需要对react的整个流程了解,对于原理也是需要了解的,可以推荐看下卡颂的react原理揭秘。看源码,必定是带着疑惑去的,解答自己的疑惑,看源码没必要钻牛角尖,忽略没必要的细节,例如某些没必要的参数,主要是搞清主流程

首先,需要对react的渲染流程需要有个大致的理解

渲染流程

React 整体的渲染流程就是 render(调度加调和) + commit(操作真实dom,执行effect回调)。可中断是在render阶段,commit是一并提交的

jsx的本质就是createElement,可以看下我前面关于react的文章。createElement执行产生了虚拟dom,虚拟dom在reconcile阶段与缓存的fiber树diff,产生新的fiber,其实也就是在旧fiber树上更改,这个阶段打上了标签,表示对应节点更改,删除,还是新增,这里有两种tag,一种表示的是节点本身的更新,另一种是subtreeTag,表示的是子节点是否更改(tag是作用在了commit阶段)。并且一般hooks也是在这个阶段处理的,例如useMemo,useCallBack的更新,但是对于useEffect,useEffectLayout的回调执行则是在commit阶段最开始就去处理了的。

初始的挂载流程(这里贴下源代码,我这里做了部分删减,保留了主流程)

从入口开始

ReactDom.createRoot(root).render(App)

创建根节点createRoot方法

这个阶段就是跟我我们传入的root创建容器,给容器和hostRootFiber之间建立关联关系并且合成事件也是在这个阶段处理的

export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
// 创建根节点,全局唯一
  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
// 标志为hoostRoot节点(后续不同类型节点会分流处理)
  markContainerAsRoot(root.current, container);

  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  // 处理合成事件
  listenToAllSupportedEvents(rootContainerElement);
// 绑定render方法
  return new ReactDOMRoot(root);
}

ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(
  children: ReactNodeList,
): void {
  const root = this._internalRoot;
  updateContainer(children, root, null, null);
};

updateContainer

render中执行力了updateContainer,这里主要看下updateContainer中的enqueueUpdatescheduleUpdateOnFiber

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
// current指代了hostRootFiber
  const current = container.current;
  const eventTime = requestEventTime();
// 根据当前 Fiber 节点的情况,请求一个合适的更新优先级车道 const lane = requestUpdateLane(current); // 创建一个更新对象,包含事件时间和更新优先级车道信息 const update = createUpdate(eventTime, lane); // 这里是为了赋值,element其实就是jsx转译后的createElement函数,并不是VDom, update.payload = {element}; // 将更新对象添加到当前 Fiber 节点的更新队列中并向上寻找根节点   const root = enqueueUpdate(current, update, lane); if (root !== null) { scheduleUpdateOnFiber(root, current, lane, eventTime); entangleTransitions(root, current, lane); } return lane; } 

关注一下enqueueUpdate


export function enqueueUpdate<State>(
  fiber: Fiber,
  update: Update<State>,
  lane: Lane,
): FiberRoot | null {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    return null;
  }
  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
// 绑定之前创建的update到对应fiber节点的更新队列上
  if (isUnsafeClassRenderPhaseUpdate(fiber)) {
    const pending = sharedQueue.pending;
    if (pending === null) {
      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update;
    }
    sharedQueue.pending = update;
// 最终都调用了markUpdateLaneFromFiberToRoot(向上寻找根节点,也就是一直递归return属性,
// 查看对应节点的类型是不是hostFiber)
    return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
  } else {
// 处理了另一种情况,递归调用了enqueueUpdate
    return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
  }
}

其实这里是不是会有个疑惑呢,current不就是根容器的current吗,这不就是根节点吗,为什么还需要再寻找呢。

这里应该提到双缓存了,其实这里的current时候指的当前页面展现所映射的这颗fiber树,但是也就是我们render所传入的app对应的fiber节点,但是根节点只有一个,就是createRoot所创建的,所以current并不是根节点。

scheduleUpdateOnFiber

这里主要是调用ensureRootIsScheduled

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  // 更新pendingLanes,标记根节点有一个待处理的更新,记录更新的lane和事件时间
  markRootUpdated(root, lane, eventTime);
// 这里的executionContext,RenderContext,workInProgressRoot都是创建的全局变量
// executionContext,RenderContext表示的是render阶段
  if ( (executionContext & RenderContext) !== NoLanes && root === workInProgressRoot ) {
    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
      workInProgressRootRenderPhaseUpdatedLanes,
      lane,
    );
  } else {
// 如果根节点是正在进行渲染的根节点,将更新lane。若根节点处于延迟挂起状态,则标记根节点为挂起。
    if (root === workInProgressRoot) {
      if (
        deferRenderPhaseUpdateToNextBatch ||
        (executionContext & RenderContext) === NoContext
      ) {
        workInProgressRootInterleavedUpdatedLanes = mergeLanes(
          workInProgressRootInterleavedUpdatedLanes,
          lane,·
        );
      }
      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        markRootSuspended(root, workInProgressRootRenderLanes);
      }
    }
// 调度更新
    ensureRootIsScheduled(root, eventTime);
// 同步更新进入
    if (
      lane === SyncLane &&
      executionContext === NoContext &&
      (fiber.mode & ConcurrentMode) === NoMode &&
      !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
    ) {
      resetRenderTimer();
      flushSyncCallbacksOnlyInLegacyMode();
    }
  }
}

ensureRootIsScheduled

主要关注下performConcurrentWorkOnRootscheduleCallback是将任务同步到任务队列中

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;

  // 标记了过期的任务,低优先级任务一直未得到执行,会被标记为过期任务
  markStarvedLanesAsExpired(root, currentTime);

  // 返回下一个任务的lane以及优先级
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // 表示当前没有需要处理的更新任务。此时若存在现有回调任务节点,则调用 cancelCallback 函数取消该任务,
// 并将 FiberRoot 的回调任务节点和优先级重置为 null 和 NoLane
  if (nextLanes === NoLanes) {
    // Special case: There's nothing to work on.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }
  // 获取新的回调优先级.
  const newCallbackPriority = getHighestPriorityLane(nextLanes);
  const existingCallbackPriority = root.callbackPriority;
  // 优先级一样,直接返回了,也就是重用了当前的任务
  if ( existingCallbackPriority === newCallbackPriority & ) {
    return
  }
// 将当前的任务挂起
  if (existingCallbackNode != null) {
    cancelCallback(existingCallbackNode);
  }
// 调度了新的任务
  let newCallbackNode;
  // 同步任务
  if (newCallbackPriority === SyncLane) {
    if (root.tag === LegacyRoot) {
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    if (supportsMicrotasks) {
      if (ReactCurrentActQueue.current !== null) {
        ReactCurrentActQueue.current.push(flushSyncCallbacks);
      } else {
        scheduleMicrotask(() => {
          if (
            (executionContext & (RenderContext | CommitContext)) ===
            NoContext
          ) {
            flushSyncCallbacks();
          }
        });
      }
    } else {
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
    newCallbackNode = null;
  } else {
    // 异步
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

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

进入到performConcurrentWorkOnRoot

performConcurrentWorkOnRoot

function performConcurrentWorkOnRoot(root, didTimeout) {
  currentEventTime = NoTimestamp;
  currentEventTransitionLane = NoLanes;
  const originalCallbackNode = root.callbackNode;
  // 处理了所有的副作用,例如effect的回调(effect的回调与调度挂钩),
// 所以对effect的解释是下一轮执行时,一定会执行上一轮的副作用
  const didFlushPassiveEffects = flushPassiveEffects();
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
// 同步Or异步(进行时间切片,以并发方式渲染)这里关注下异步执行
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  ensureRootIsScheduled(root, now());
  if (root.callbackNode === originalCallbackNode) {
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}

enderRootConcurrent

主要关注的是enderRootConcurrent,在这里也就进入了wookLoop,主要是初始化了workInProgress即fiber树遍历的指针

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
  // 初始化了遍历的指针即workInProgress(这是全局变量)调用createWorkInProgress初始化遍历的指针
  // 这里会判断workInProgress是否初始化了,初始化了则将root.current的某些属性赋值,没有初始化就new了一个fiber
// 然后绑定alternate(双缓存,指向了另一颗fiber对应的节点,可以看一下fiber的数据结构)
    prepareFreshStack(root, lanes);
// 这里其实只是保证workLoopConcurrent一定会被执行
  do {
    try {
      workLoopConcurrent();
      break;
    } catch (thrownValue) {
      handleThrow(root, thrownValue);
    }
  } while (true);
}

workLoopConcurrent

就是单纯的递归,处理节点的逻辑在beginWork里,shouldYield即时间片,5ms,这就是可打断了,也就没过5ms,就让出主线程,

所以这里的逻辑是每处理一个节点,就判断是否超过了当前的时间片。

这里的处理流程:先向下递,即在递的过程重创建对应的fiber节点,并且处理children(这个阶段也只是连接了sibling,但是sibling系节点的children还没处理)

然后child为空时调用completeUnitOfWork开始归,返回父节点,判断有没有有没有兄弟节点,有的话开始另一轮递的环节,也就是继续向下创建fiber节点。。。一直这样处理,知道返回了根节点,也就完成了本次的渲染

function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}


function performUnitOfWork(unitOfWork: Fiber): void {
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
unitOfWork.memoizedProps = unitOfWork.pendingProps;
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}

beginwork 。fiber树逐步向下构建的,在对应的函数里面创建了fiber节点处理了。。。。然后返回的child

伪代码 拿到当遍历的哪个fiber节点,然后根据节点内心
export const beginWork = (wip: FiberNode) => {
  // 比较,返回子fiberNode
  switch (wip.tag) {
    case 根节点(hostRootFiber):
      return ()=>fiber.Child;
    case 函数组件:
      return ()=>fiber.Child;
    case 类组件:
      return null;
    case memo:
      return ()=>fiber.Child;
 case forwordRef:
return ()=>fiber.Child;
    default:
      console.warn('beginWork未实现的类型');
      break;
  }
  return null;
};

这里就先看下函数组件如何处理的,其他组件大致处理的逻辑是差不多的

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
  let context;
  let nextChildren;
  let hasId;
    hasId = checkDidRenderIdHook();
    nextChildren = renderWithHooks(
      current,
      workInProgress,
      Component, // workInProgress.type 其实就是函数 并不是被转译的jsx
      nextProps,
      context,
      renderLanes,
    );
    hasId = checkDidRenderIdHook();
  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

这里就关注下renderWithHooksreconcileChildren

reconcileChildren是做调和的,也就是Vdom转fiber。 也就是diff阶段,这个阶段会做一个fiber节点的复用,也会打上tag(表明自身节点的新增,修改,删除),但是还有subTreeTag(当前节点下的子节点的tag和)是怎么标记的,是在归过程中计算的,即父节点的subTreeTag就等于对应的子节点的subTreeTag和。

还有一个问题,节点的其实只会做新增和删除,并不会去修改其属性,那么为什么有更新的tag呢?因为节点的顺序变了,

renderWithHooks 就是hooks 的实现

这里后面补一下,内容有点多。。。。记个TODO

这时假设已经到底了,即next=== null 跳回到workLoopConcurrent

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

completeUnitOfWork

  1. 创建真实的dom节点,但是还没渲染到页面中
  2. 处理flags,合并位subTreeFlags(flags是自己节点有没有更改,subTreeFlags是表面自己节点下面的子节点是否有更改。作用在commit阶段)
  3. 建立真实dom的关系,把子dom插入到父dom中

就是由低向上归即在这个阶段创建的真实dom节点,绑定到了对应fiber节点的stateNode属性上,这里的部分比较复杂后续跟beginWork,将流程补齐。

commit阶段

进入commit阶段 即在performConcurrentWorkOnRoot中,根据exitStatus的状态判断如何处理渲染的结果

performConcurrentWorkOnRoot

完成了直接提交

finishConcurrentRender(root, exitStatus, lanes);
完成状态直接commitRoot
function finishConcurrentRender(root, exitStatus, lanes) {
  switch (exitStatus) {
      case RootCompleted: {
      commitRoot(
        root,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
      );
      break;
    }
    default: {
      throw new Error('Unknown root exit status.');
    }
  }
}

commitRoot

处理副作用,即effect的回调函数这里会将回调函数投入到任务队列中去,是与调度挂钩的

根据subtreeTag判断节点是否有更改,是否有在BeforeMutationMask | MutationMask | LayoutMask | PassiveMask阶段需要执行的任务,如果说不存在的话直接将root节点的current指针指向缓存中的那颗fiber树即可,

function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: EventPriority){
// 处理副作用
flushPassiveEffects()
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
// before mutation
 commitBeforeMutationEffects(
root,
finishedWork,
);
// mutation 根据fiber节点的tag和subTreeTag增删改 dom,
commitMutationEffects(root, finishedWork, lanes);
// layout 在这个阶段更新ref,同步的执行useLayoutEffect的回调等
commitLayoutEffects(finishedWork, root, lanes);
}else {
root.current = finishedWork;
}
}

记个TODO  补齐下react的合成事件原理,hooks实现原理,diff的过程,还有就是react的调度机制。

 

- THE END -
1

共有 0 条评论