React中透过render函数学习(二)——workLoop->performUnitOfWork->beginWork->updateHostRoot->reconcileChildren

React 18 中 updateContainer 方法的简化实现,其中包含了一些重要的操作,如创建更新对象、将更新任务加入更新队列、调度更新等。这一过程体现了 React 内部如何协调渲染过程,尤其是如何在 Fiber 架构下处理更新。让我们逐步分析这个方法的工作原理。

updateContainer 方法的实现分析

export function updateContainer(
  element: ReactElementType | null,
  root: FiberRootNode
) {
  // 获取根 Fiber 节点
  const hostRootFiber = root.current;

  // 创建更新对象,包含新的 React 元素
  //就是返回传入的element <App/>
  const update = createUpdate<ReactElementType | null>(element);

  // 将更新对象加入更新队列
  enqueueUpdate(
    hostRootFiber.updateQueue as UpdateQueue<ReactElementType>,
    update
  );

  // 调度更新任务
  scheduleUpdateOnFiber(hostRootFiber);

  // 返回传入的 element(React 元素)
  return element;
}

1. 获取根 Fiber 节点

const hostRootFiber = root.current;
  • root 是通过 ReactDOM.createRoot() 创建的根节点对象,它表示根组件的 Fiber 树。root.current 获取的是 FiberRootNode,即 Fiber 树的根节点。
  • hostRootFiber 是根节点的 Fiber 节点,它是渲染树的顶层节点,负责协调该树的更新。

2. 创建更新对象——createUpdate

const update = createUpdate<ReactElementType | null>(element);
  • createUpdate 函数创建了一个新的 更新对象,该对象表示即将更新的 React 元素。这里,element 是传入的新的 React 元素(可以是一个组件、JSX、或者 null)。
  • createUpdate 通常会包含以下内容:
    • 新的 React 元素(即 element
    • 更新的优先级(这取决于任务的调度)
    • 更新的类型(例如,常规更新、异步更新等)

3. 将更新加入更新队列——enqueueUpdate

enqueueUpdate(
  hostRootFiber.updateQueue as UpdateQueue<ReactElementType>,
  update
);
  • enqueueUpdate 将创建的 update 对象加入到 更新队列 中。hostRootFiber.updateQueue 是 Fiber 树中根节点对应的更新队列,update 就是包含新元素信息的更新任务。
  • 更新队列是一个用于管理和调度更新任务的结构,React 将所有的更新任务都先存放在更新队列中,随后进行批量处理。
    在这里插入图片描述

4. 调度更新任务——scheduleUpdateOnFiber

scheduleUpdateOnFiber(hostRootFiber);

//具体代码
function scheduleUpdateOnFiber(fiber: Fiber) {
  // 步骤 1: 根据 Fiber 节点生成更新
  const root = makeUpdateFromFiberToRoot(fiber);
  // 步骤 2: 触发渲染过程
  renderRoot(root);
}

4.1 向上查找根节点——makeUpdateFromFiberToRoot

其中makeUpdateFromFiberToRoot方法递归得到顶层节点。它负责从当前的 Fiber 节点递归地向上走,直到到达根节点。通过这种递归的方式,React 将更新从单一的组件(Fiber 节点)传递到整个渲染树。

function makeUpdateFromFiberToRoot(fiber: Fiber) {
  // 步骤 1: 从 Fiber 节点找到根节点
  let node = fiber;
  while (node.return !== null) {
    node = node.return;
  }

  // `node` 现在是根节点,根节点也可以称作 FiberRoot 节点
  const root = node.stateNode;
  
  // 创建并返回更新对象
  return root;
}

解析:
向上查找根节点:makeUpdateFromFiberToRoot 会从当前的 fiber 节点开始,沿着 fiber.return 链向上查找,直到找到根节点(FiberRootNode)。
生成更新对象:一旦到达根节点,React 会生成一个更新对象,代表当前的更新任务(例如,某个组件的状态变化或新的 JSX 渲染)。

4.2 触发渲染过程——renderRoot

renderRoot 是一个函数,用于触发渲染工作。它通常会调用并递归地执行一系列的渲染任务,直到完成整个更新过程。这个过程包括执行 Fiber 树中的 beginWork 和 completeWork,以及渲染新状态或 DOM。

function renderRoot(root: FiberRootNode) {
	//双缓存机制,将current复制一层给workInProgress
	//React 使用两棵 Fiber 树(current 和 workInProgress)来实现双缓存
  const { current } = root; // 获取当前的 Fiber 树的根节点
  let workInProgress = current;

  // 启动渲染任务(并发渲染模式下会启动任务)
  workInProgress = performUnitOfWork(workInProgress);

  // 继续调度工作单元
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

renderRoot

React 的渲染过程可以分为多个阶段,包括:更新(Reconciliation)、渲染(Rendering)、提交(Commit)等。

//更细节的
function renderRoot(root: FiberRootNode) {
  try {
    // 初始化
    prepareFreshStack(root);
 
    // 开始工作循环
    do{
	   try{
	      workLoop();
          break;
  } catch (e) {
    console.warn('workLoop发生错误', e);
    workInProgress = null;
  }while (true); 
 
  // 获取完成的工作单元
  const finishedWork = root.current.alternate;
  root.finishedWork = finishedWork; 
 
  // 提交根节点的wip fiberNode树,并处理flags
  commitRoot(root);

prepareFreshStatck()

prepareFreshStatck()中的createWorkInProgress简单理解就是上面将current复制一层给workInProgress, const { current } = root; // 获取当前的 Fiber 树的根节点 let workInProgress = current;,两棵 Fiber 树来实现双缓存
在这里插入图片描述
复制过后的双缓存 数据结构
在这里插入图片描述

workLoop()

function workLoop(){
	while(workInProgress !== null)
	performUnitOfWork(workInProgress);
}

performUnitOfWork(workInProgress) 【递归】

function performUnitOfWork(fiber: FiberNode) {
  // 开始对当前Fiber节点进行工作
  // 这里的“递”可能指的是递归处理子节点
  const next = beginWork(fiber); // 执行beginWork函数,返回下一个需要处理的Fiber节点
 
  // 更新当前Fiber节点的memoizedProps为pendingProps
  // 这通常意味着将传入的props“确认”为当前节点的属性
  fiber.memoizedProps = fiber.pendingProps;
 
  // 判断下一个需要处理的节点是否为null
  // 如果为null,可能表示当前节点没有子节点或子节点已经处理完毕
  if (next === null) {
    // 这里的“归”可能指的是回溯到父节点,完成当前节点的工作
    completeUnitOfWork(fiber); // 执行completeUnitOfWork函数,完成当前Fiber节点的工作
  } else {
    // 如果还有下一个节点需要处理,则更新workInProgress指针
    // workInProgress通常指向当前正在处理的Fiber节点
    workInProgress = next; // 更新workInProgress为下一个需要处理的Fiber节点
    // 注意:这里通常会有一个递归调用performUnitOfWork(next),但在您提供的代码片段中省略了
  }
}
beginWork()

beginWork 主要发生在协调阶段,它被调用来处理和更新 React 虚拟 DOM(Fiber 树)。
1.初始化 Fiber 节点的工作:当 React 开始处理某个 Fiber 节点时,beginWork 会被调用。它会检查该节点的当前状态,并决定是否需要进行更新。
2.调度子节点的更新:如果当前节点有子节点,beginWork 会为这些子节点安排进一步的更新工作。
在执行过程中,beginWork 会遍历 Fiber 树,判断是否需要更新(例如,检查 props 或 state 是否发生了变化),然后决定是否继续向下渲染(即继续对子节点调用 beginWork)。

export const beginWork = (wip: FiberNode): FiberNode | null => {
  // 根据Fiber节点的类型,执行相应的更新逻辑,并返回下一个需要处理的Fiber节点
  switch (wip.tag) {
    case 'HostRoot':
      return updateHostRoot(wip); // 处理根节点
    case 'HostComponent':
      return updateHostComponent(wip); // 处理宿主组件(如DOM元素)
    case 'HostText':
      return null; // 文本节点不需要进一步处理,直接返回null
    case 'FunctionComponent':
      return updateFunctionComponent(wip); // 处理函数组件
    default:
      console.warn('beginWork未实现的类型'); // 输出警告信息
      break; // 注意:这里的break是多余的,因为return已经会退出函数
      // 但为了保持格式一致性和清晰性,我们暂时保留它
      return null; // 对于未实现的类型,返回null
  }

beginWork初次执行完, 此时内存状态,wip和h1对应的fiber对象建立联系,并且给h1 fiber打上flags标记。
在这里插入图片描述

updateHostRoot

在这里插入图片描述

function updateHostRoot(wip: FiberNode): FiberNode | null {
  // 获取当前工作单元(Fiber)的基态
  const baseState = wip.memoizedState as Element; // 假设memoizedState是Element类型
 
  // 获取更新队列,并断言其类型为UpdateQueue<Element>
  const updateQueue = wip.updateQueue as UpdateQueue<Element>;
 
  // 从共享对象中取出待处理的更新,并清空待处理队列
  const pending = updateQueue.shared.pending;
  updateQueue.shared.pending = null;
 
  // 使用processUpdateQueue函数处理待处理的更新,并获取更新后的状态
  // 注意:这里修正了memoizedstate的拼写错误,并添加了正确的类型断言
  const { memoizedState } = processUpdateQueue(baseState, pending) as { memoizedState: Element };
 
  // 更新当前工作单元的基态为最新状态
  wip.memoizedState = memoizedState;
 
  // 获取更新后的子节点,这里假设memoizedState直接代表了子节点
  // 注意:这里的逻辑可能需要根据实际情况调整,因为memoizedState可能并不直接等于子节点
  const nextChildren = wip.memoizedState as Element[]; // 假设这里是Element数组,但需要根据实际情况确定
 
  // 调用reconcileChildren函数来协调(渲染)子节点
  // 注意:函数名可能是reconcileChildren的一个拼写错误,通常应该是reconcileChildren或者类似的名称,但这里按照您提供的名称使用
  reconcileChildren(wip, nextChildren);
 
  // 返回当前工作单元的第一个子节点,以便后续的工作单元可以继续处理
  // 注意:如果wip.child是null,则表示没有子节点需要处理
  return wip.child;
}
 
processUpdateQueue

在这里插入图片描述

export const processUpdateQueue = <state>(
  baseState: state,
  pendingUpdate: Update<state> | null
): { memoizedState: state } => {
  // 初始化结果对象,其memoizedState属性设置为baseState
  const result: { memoizedState: state } = {
    memoizedState: baseState
  };
 
  // 检查是否有待处理的更新
  if (pendingUpdate !== null) {
    const action = pendingUpdate.action;
 
    // 如果action是一个函数,则执行它并更新memoizedState
    if (typeof action === 'function') {
      result.memoizedState = action(baseState);
    } else {
      // 如果action不是函数,则直接将其值赋给memoizedState
      // 注意:这里假设action的类型与state兼容
      result.memoizedState = action as state; // 需要类型断言来确保TypeScript不会报错
    }
  }
 
  // 返回结果对象
  return result;
};

在这里插入图片描述
在这里插入图片描述
** 注:Fiber对象数据结构 **

reconcileChildren(★★★)

reconcileChildren 主要处理组件的 子树,对于每一个子节点(即子 Fiber 节点)会执行以下操作:

  1. 子节点的类型判断(会首先判断每个子节点的类型【比如是 DOM 元素、函数组件还是类组件等】,然后根据不同的类型来决定如何处理)
  2. 节点的比较(相同类型的节点/不同类型的节点/key 和索引)
  3. 生成新的 Fiber 节点(为需要更新或新创建的子组件生成新的 Fiber 节点)
  4. 处理子树的递归(beginWork中递归调用)(会递归地调用自己来处理子组件。如果某个子组件有子节点,React 会继续对子节点进行协调,直到所有节点都被处理完)
    在这里插入图片描述
    在这里插入图片描述
const App:any=function (){
	return(
	<h1>
		<h2>
			<h3>3333</h3>
		</h2>
	</h1>
	)
}

初次被调用执行, 此时内存状态,wip和h1对应的fiber对象建立联系,并且给h1 fiber打上flags标记。
在这里插入图片描述

5. 返回元素

return element;
  • 最终,updateContainer 会返回传入的 element,即新的 React 元素。这只是为了保持 API 的一致性,并不会影响更新流程。

❤ 整体流程解析

  1. 接收新的元素:首先,updateContainer 接收到新的 React 元素,这可能是一个新的组件或更新后的 JSX 树。
  2. 创建更新对象:通过 createUpdate 创建一个新的更新对象。这个对象将存储新的元素以及更新的必要信息。
  3. 加入更新队列:更新对象会被加入到根节点的更新队列中,这样 React 就知道要在后续的渲染过程中处理这个更新。
  4. 调度更新scheduleUpdateOnFiber 会将这个更新任务调度到渲染队列中,等待在合适的时机进行渲染。React 会根据任务的优先级,决定何时开始执行更新操作。
  5. 返回新的元素:虽然 updateContainer 最终返回了新的元素,但是这只是为了 API 一致性,并不会影响实际的更新过程。

❤ Fiber 树与并发渲染

在 React 18 中,Fiber 架构允许 React 在更新过程中分片任务,并在空闲时执行低优先级任务。通过 Fiber,React 能够在不阻塞主线程的情况下处理复杂的 UI 更新。

  • Fiber 是 React 内部用于协调渲染的结构,表示了整个组件树的每个单独的部分。
  • 更新队列 管理着所有的更新任务,每个任务都有一个 优先级,React 会根据优先级决定任务的执行顺序。
  • 调度器(Scheduler) 决定任务何时执行,React 18 引入了更细粒度的调度,使得 UI 更新更加高效,避免了长时间的 UI 卡顿。

❤ 关键点总结

  • updateContainer 是一个用于调度更新的核心函数,它通过创建更新对象、加入更新队列并调度更新任务来管理渲染更新。
  • createUpdate 创建一个新的更新任务,其中包含了新的 React 元素。
  • enqueueUpdate 将更新对象加入到更新队列。
  • scheduleUpdateOnFiber 将更新任务调度到 Fiber 渲染队列中,执行时会根据任务的优先级进行调度。
  • React 18 引入了 并发渲染,通过任务优先级和调度器,能够有效地在后台渲染和调度 UI 更新,避免 UI 卡顿,提升响应速度。

通过这样的机制,React 能够以更加高效的方式管理和渲染 UI,尤其适用于复杂的和交互频繁的应用。

reconcileChildren 详解

在 React 的 Fiber 系统中,reconcileChildren 是一个关键的函数,用于处理组件的子节点的更新逻辑。它负责将新旧虚拟 DOM(Fiber)树进行比较,并决定如何更新和重排子组件。这一过程被称为“协调” (Reconciliation),是 React 核心性能优化的一个重要环节。

1. 协调(Reconciliation)的概念

协调是 React 更新 UI 时的核心任务。当组件的状态或属性发生变化时,React 会触发一个协调过程,比较当前(旧)虚拟 DOM 和即将更新(新)虚拟 DOM 之间的差异,并计算出最小的变更来高效地更新 DOM。React 通过 reconcileChildren 来执行这一过程,尤其是在处理组件的子节点时。

2. reconcileChildren 的作用

reconcileChildren 的主要作用是:

  • 比较新旧子树的差异:检查父组件的子节点(例如,渲染函数的返回值)是否发生变化。
  • 生成新的 Fiber 节点:根据差异的类型,生成新的 Fiber 节点,并为其分配必要的更新工作。
  • 决定节点类型和更新方式:根据不同的子节点类型(如类组件、函数组件、DOM 元素等),决定如何更新 Fiber 树。

reconcileChildren 会遍历父组件的子节点,并执行以下操作:

  1. 匹配旧的 Fiber 节点与新的子节点
  2. 根据变化创建新的 Fiber 节点,为更新的组件分配新的任务。
  3. 为不可复用的节点创建新的 Fiber 节点,如果发现类型、键值或顺序有变化,就创建新的节点并丢弃旧节点。

3. reconcileChildren 的执行流程

reconcileChildren 主要处理组件的 子树,对于每一个子节点(即子 Fiber 节点)会执行以下操作:

1. 子节点的类型判断

reconcileChildren 会首先判断每个子节点的类型(比如是 DOM 元素、函数组件还是类组件等),然后根据不同的类型来决定如何处理:

  • DOM 元素:对于 DOM 元素,React 会比较当前节点和新的节点的类型是否一致。如果一致,则可以复用 Fiber 节点;如果不一致,则需要删除旧的 Fiber 节点并创建新的节点。
  • 组件:对于 React 组件,reconcileChildren 会调用不同的协调逻辑,比如对于函数组件、类组件、以及类组件的生命周期方法,React 会根据类型进行相应的更新和复用。
  • 文本节点:React 会直接比较文本内容的变化,决定是否需要更新文本节点。
2. 节点的比较

在比较子节点时,React 会使用以下几种方法来判断节点是否可复用,或者是否需要创建新的节点:

  • 相同类型的节点:如果旧的节点和新的节点类型相同(例如,都为 <div>),React 会尝试复用旧的节点并更新它的属性。
  • 不同类型的节点:如果新旧节点类型不同(例如,原来是 <div>,现在变成了 <span>),React 会销毁旧节点并创建一个新的节点。
  • key 和索引:React 会根据 key 来判断是否是同一个节点。使用 key 可以帮助 React 更有效地识别哪些节点是重用的,哪些是新的。
3. 生成新的 Fiber 节点

根据对比结果,reconcileChildren 会为需要更新或新创建的子组件生成新的 Fiber 节点。这些新的 Fiber 节点会包含新的虚拟 DOM 状态、属性、事件等信息。

4. 处理子树的递归

reconcileChildren 会递归地调用自己来处理子组件。如果某个子组件有子节点,React 会继续对子节点进行协调,直到所有节点都被处理完。

4. 不同类型的子组件的协调

React 在协调过程中,会根据子组件的类型采用不同的处理策略:

  • 函数组件和类组件:如果新的子节点是一个函数组件或类组件,React 会创建一个新的 Fiber 节点,并将其与该组件对应的生命周期方法(如 componentDidMountcomponentDidUpdate 等)关联。
  • 原生 DOM 节点:如果新的子节点是一个 DOM 元素,React 会通过直接对比当前 DOM 树的变化来决定是否需要更新、删除或重建该节点。
  • 文本节点:文本节点比较简单,React 会直接对比文本内容的变化,决定是否更新文本。

5. reconcileChildrenfiber 节点的关系

在 React 的 Fiber 系统中,每个虚拟 DOM 节点都对应一个 Fiber 节点。Fiber 节点是一种数据结构,用来表示 React 树中的每一个节点,包含关于该节点的详细信息,如:

  • 节点类型(是一个函数组件、类组件、DOM 节点还是文本节点)。
  • 父节点和子节点的引用。
  • 更新状态、优先级、挂起的任务等。

reconcileChildren 通过处理 Fiber 节点的方式,维护了组件树的更新逻辑。每当 React 需要重新渲染组件时,它会调用 reconcileChildren 来决定如何更新子节点。

6. 性能优化

React 的 reconcileChildren 采用了许多优化策略,旨在提高渲染效率。以下是几个关键的优化:

  • 双端比较(双向遍历):React 在对比子节点时,采用从两端向中间对比的策略(即从头和尾两端同时进行匹配),从而最大限度地复用节点,减少 DOM 操作。
  • key 的作用:通过给子组件添加唯一的 key 属性,React 能够更容易地识别和复用节点,避免不必要的 DOM 操作。
  • 最小化更新:React 通过协调算法,只会更新发生变化的部分,避免重新渲染整个组件树,从而减少了性能消耗。

7. 总结

reconcileChildren 是 React Fiber 中的一个核心函数,它负责协调父组件的子节点的变化。在更新过程中,React 会根据新旧节点的对比结果,决定哪些节点需要更新、哪些节点需要重新创建,从而高效地更新 UI。这个过程确保了 React 在渲染时具有极高的性能,同时也能处理复杂的组件树和高效的更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FE_Jinger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值