searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

简述 React 渲染的重要阶段

2024-07-16 09:32:20
20
0

将 React 组件呈现到页面上需要经历下面三个步骤:

  1. 触发渲染
  2. 渲染组件
  3. 提交到 DOM

触发渲染

以下两种情况会触发渲染:

  1. 初次渲染
  2. 组件(或者组件的祖先)状态发生改变
import { createRoot } from "react-dom/client";
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    // 状态改变触发再次渲染
    setCount(count + 1);
  }

  return <button onClick={handleClick}>You pressed me {count} times</button>;
}

const root = createRoot(document.getElementById("root"));
// 初次渲染
root.render(<Counter />);

安排渲染任务

触发渲染后,React 并没有立即开始渲染工作,而是将渲染任务做了计划,具体何时执行需要听从调度。

我们来看一看 React 大致是如何处理的。

触发渲染后总会进入scheduleUpdateOnFiber函数:

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number
) {
  ensureRootIsScheduled(root, eventTime);
}

然后进入ensureRootIsScheduled函数:

// Use this function to schedule a task for a root.
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // Determine the next lanes to work on, and their priority.
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );

  // We use the highest priority lane to represent the priority of the callback.
  const newCallbackPriority = getHighestPriorityLane(nextLanes);

  // 判断优先级
  if (includesSyncLane(newCallbackPriority)) {
    // 优先级高
    // Special case: Sync React callbacks are scheduled on a special internal queue

    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    if (supportsMicrotasks) {
      // 如果支持微任务
      // Flush the queue in a microtask.
    } else {
      // 调用scheduler package的API以高优先级安排任务
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
  } else {
    // 优先级不高
    // 调用scheduler package的API以其他优先级安排任务

    scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );
  }
}

我们来看看这个函数大致做了什么:

  • 确定 Lanes 和优先级(​Lanes 模型是 React 内部调度的一个重要模型,暂时不深入了解​)
    • 优先级很高:安排为同步回调,回调函数是performSyncWorkOnRoot
      • 如果支持微任务,会在微任务中来调用回调函数,源码中使用微任务可能通过queueMicrotask | Promise等方式(​主流浏览器都支持 queueMicrotask​)
      • 否则使用 scheduler 的 API 来实现回调
    • 优先级不高:安排为异步回调,回调函数是performConcurrentWorkOnRoot
      • 使用 scheduler 的 API 来实现回调

scheduler

scheduler是 React 中一个用于任务调度的包,现在仅在 React 中使用,但是完全可以独立出来作为通用调用算法。每一个任务都有优先级,将任务放在最小堆中,每次取出优先级最高的任务执行,执行任务是通过MessageChannel的端口发送和监听消息来完成的,属于事件循环中的任务。

为什么选择 MessageChannel

为了实现 0ms 延时的定时器,setTimeout(fn, 0)无法做到零延时,更多可以了解 MDN 上的解析。

渲染阶段

两种情况下 React 在渲染阶段做的工作:

  • 初次渲染:创建 DOM 节点
  • 再次渲染:计算与上一次渲染之间的差异,此阶段不会使用差异信息做实际性修改,那是下一个阶段的工作

在上面一个章节我们看到 React 为渲染安排了回调:performSyncWorkOnRootperformConcurrentWorkOnRoot,它们的主要调用流程如下:

function performSyncWorkOnRoot(root: FiberRoot) {
  renderRootSync(root);
  root.finishedWork = root.current.alternate;
}
function performSyncWorkOnRoot(root: FiberRoot) {
  // We disable time-slicing in some cases: if the work has been CPU-bound
  // for too long ("expired" work, to prevent starvation), or we're in
  // sync-updates-by-default mode.
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  root.finishedWork = root.current.alternate;
}

他们调用的renderSyncRoot | renderRootConcurrent函数中最主要的就是工作循环。

function renderRootSync(root: FiberRoot, lanes: Lanes) {
  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we'll continue where we left off.
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);
  }
  workLoopSync();
}
function renderRootSync(root: FiberRoot, lanes: Lanes) {
  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we'll continue where we left off.
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);
  }
  workLoopConcurrent();
}

工作循环

下面是这两种工作循环的代码。

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

workInProgess

workInProgress就是当前需要处理的 Fiber,可以调用下面的函数创建它:

// This is used to create an alternate fiber to do work on.
function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode
    );
  } else {
  }

  return workInProgress;
}

我们从 React 源码中的注释中看到这个函数使用了双缓冲池,如果current.alternate === null时才创建新的 Fiber,否则可以重用之前创建的 Fiber。

那么进入工作循环的第一个workInProgress是什么呢?

答案是:renderRootSyncrenderRootConcurrent中调用prepareFreshStack创建。

function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  workInProgress = rootWorkInProgress;
}

这里FiberRoot类型的root参数是哪里来的呢?

答案是:调用 react-dom package 的 Client API createRoot(domNode, options?)时创建的,reactDOMRoot._internalRoot就是FiberRootFiberRoot.current是 Fiber 节点,我们称它为 HostRoot。

两种工作循环的差别

它们的唯一区别是在调用performUnitOfWork前是否判断shouldYield()以便让出主线程,这个 API 是 scheduler package 提供的,它的精简代码如下:

function shouldYieldToHost() {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < frameInterval) {
    // The main thread has only been blocked for a really short amount of time;
    // smaller than a single frame. Don't yield yet.
    return false;
  }
  return true;
}

贴士:源码中本来还有更多判断,例如使用 Facebook 和 Chrome 合作的 API navigator.scheduling.isInputPending(),但是由于 React 目前默认没有开启这个功能,所以代码精简了。

如果占用主线程时间超出frameInterval,那么就需要让出主线程,frameInterval的初始值是 5ms。

performUnitOfWork

如果满足工作循环的判断,那么就会进入performUnitOfWork,下面是这个函数的精简版本:

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

这个函数非常好理解:

  • 调用beginWork
    • 如果没有返回下一个 Fiber,那么就调用completeUnitOfWork
    • 否则让workInProgress指向下一个 Fiber,进入下一次工作循环。

beginWork

我们暂时不深入了解这个函数,现在我们仅仅需要关注它的返回值,它始终返回workInProgress.child或者null,值得注意的是workInProgress.child也可能是null

completeUnitOfWork

如果 beginWork 返回 null,意味着这个分支已经没有需要处理的 Fiber 了,那么就可以完成当前这个 Fiber,然后可以接着处理它的兄弟节点,然后返回父节点。

下面是这个函数的精简版本:

function completeUnitOfWork(unitOfWork) {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork = unitOfWork;
  do {
    const next = completeWork(unitOfWork);
    if (next !== null) {
      // Completing this fiber spawned new work. Work on that next.
      workInProgress = next;
      return;
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }

    // Otherwise, return to the parent
    completedWork = completedWork.return;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);
}

渲染阶段结束

到这里渲染阶段就结束了,可以看到实际工作是在 beginWork 和 completeWork 中完成的,但是我们目前还没有深入了解这两个函数。

可能到这里我们有一个疑问,渲染阶段到底产出了什么呢?

答案是:生成了一个“全新”的 Fiber tree,之所以加引号,是因为并非所有的 Fiber 都是新创建的,可能是重用了之前的 Fiber,其中的 Fiber 有可能还标记了副作用。

FiberRootNode.finishedWork还指向了这个新的 Fiber tree,这在下一个阶段中有使用到。

什么是副作用

副作用这个词从字面上很难理解,React 文档中有一些关于副作用的解释。在计算机科学中,副作用表示对于函数外的变量,修改了参数等等,例如在事件处理函数中更改状态,发送 http 请求,导航到其他页面等等都是副作用。我们熟知的 Hook 还有类组件的一些生命周期方法都是副作用。

还记得我们之前提到渲染阶段必须是纯函数,不能有任何副作用,否则 UI 将不受控制,所以在渲染阶段只是将副作用标记在 Fiber 上,等进入提交阶段再执行副作用。

Fiber 对象中有一些属性就是专门为副作用设置的:

{
  // Effect
  flags: Flags,
  subtreeFlags: Flags,
  deletions: Array<Fiber> | null,

  // Singly linked list fast path to the next fiber with side-effects.
  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,
}

flags中就保存了副作用的 flag,例如Placement | Update | ChildDeletion,值得注意的是flags中可能保存了很多副作用。

原来的设计中,属性nexeEffect使得有副作用的 Fiber 可以串联成一个链,但是后来不再使用nextEffect | firstEffect | lastEffect,而是去遍历整颗树。

提交阶段

提交阶段可以拆分成下面几个子阶段:

  • before mutation 阶段
    对 host tree(例如 DOM 树)做出修改前,例如类组件的getSnapshotBeforeUpdate在这个阶段被调用。
  • mutation 阶段
    插入,修改,删除 DOM 节点等等。
  • layout 阶段
    修改 host tree 后,在浏览器进行绘制前,例如类组件的componentDidMount | componentDidUpdate在这个阶段被调用。

提交阶段主要包括在commitRoot函数中,它的精简版本如下:

function commitRoot(root: FiberRoot) {
  const finishedWork = root.finishedWork;
  // The commit phase is broken into several sub-phases. We do a separate pass
  // of the effect list for each phase: all mutation effects come before all
  // layout effects, and so on.

  // The first phase a "before mutation" phase. We use this phase to read the
  // state of the host tree right before we mutate it. This is where
  // getSnapshotBeforeUpdate is called.
  commitBeforeMutationEffects(root, finishedWork);

  // The next phase is the mutation phase, where we mutate the host tree.
  commitMutationEffects(root, finishedWork);

  // The work-in-progress tree is now the current tree. This must come after
  // the mutation phase, so that the previous tree is still current during
  // componentWillUnmount, but before the layout phase, so that the finished
  // work is current during componentDidMount/Update.
  root.current = finishedWork;

  // The next phase is the layout phase, where we call effects that read
  // the host tree after it's been mutated. The idiomatic use case for this is
  // layout, but class component lifecycles also fire here for legacy reasons.
  commitLayoutEffects(finishedWork, root, lanes);
}

到这里渲染和提交阶段就结束了。

0条评论
0 / 1000
何****宇
1文章数
0粉丝数
何****宇
1 文章 | 0 粉丝
何****宇
1文章数
0粉丝数
何****宇
1 文章 | 0 粉丝
原创

简述 React 渲染的重要阶段

2024-07-16 09:32:20
20
0

将 React 组件呈现到页面上需要经历下面三个步骤:

  1. 触发渲染
  2. 渲染组件
  3. 提交到 DOM

触发渲染

以下两种情况会触发渲染:

  1. 初次渲染
  2. 组件(或者组件的祖先)状态发生改变
import { createRoot } from "react-dom/client";
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    // 状态改变触发再次渲染
    setCount(count + 1);
  }

  return <button onClick={handleClick}>You pressed me {count} times</button>;
}

const root = createRoot(document.getElementById("root"));
// 初次渲染
root.render(<Counter />);

安排渲染任务

触发渲染后,React 并没有立即开始渲染工作,而是将渲染任务做了计划,具体何时执行需要听从调度。

我们来看一看 React 大致是如何处理的。

触发渲染后总会进入scheduleUpdateOnFiber函数:

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number
) {
  ensureRootIsScheduled(root, eventTime);
}

然后进入ensureRootIsScheduled函数:

// Use this function to schedule a task for a root.
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // Determine the next lanes to work on, and their priority.
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );

  // We use the highest priority lane to represent the priority of the callback.
  const newCallbackPriority = getHighestPriorityLane(nextLanes);

  // 判断优先级
  if (includesSyncLane(newCallbackPriority)) {
    // 优先级高
    // Special case: Sync React callbacks are scheduled on a special internal queue

    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    if (supportsMicrotasks) {
      // 如果支持微任务
      // Flush the queue in a microtask.
    } else {
      // 调用scheduler package的API以高优先级安排任务
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
  } else {
    // 优先级不高
    // 调用scheduler package的API以其他优先级安排任务

    scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );
  }
}

我们来看看这个函数大致做了什么:

  • 确定 Lanes 和优先级(​Lanes 模型是 React 内部调度的一个重要模型,暂时不深入了解​)
    • 优先级很高:安排为同步回调,回调函数是performSyncWorkOnRoot
      • 如果支持微任务,会在微任务中来调用回调函数,源码中使用微任务可能通过queueMicrotask | Promise等方式(​主流浏览器都支持 queueMicrotask​)
      • 否则使用 scheduler 的 API 来实现回调
    • 优先级不高:安排为异步回调,回调函数是performConcurrentWorkOnRoot
      • 使用 scheduler 的 API 来实现回调

scheduler

scheduler是 React 中一个用于任务调度的包,现在仅在 React 中使用,但是完全可以独立出来作为通用调用算法。每一个任务都有优先级,将任务放在最小堆中,每次取出优先级最高的任务执行,执行任务是通过MessageChannel的端口发送和监听消息来完成的,属于事件循环中的任务。

为什么选择 MessageChannel

为了实现 0ms 延时的定时器,setTimeout(fn, 0)无法做到零延时,更多可以了解 MDN 上的解析。

渲染阶段

两种情况下 React 在渲染阶段做的工作:

  • 初次渲染:创建 DOM 节点
  • 再次渲染:计算与上一次渲染之间的差异,此阶段不会使用差异信息做实际性修改,那是下一个阶段的工作

在上面一个章节我们看到 React 为渲染安排了回调:performSyncWorkOnRootperformConcurrentWorkOnRoot,它们的主要调用流程如下:

function performSyncWorkOnRoot(root: FiberRoot) {
  renderRootSync(root);
  root.finishedWork = root.current.alternate;
}
function performSyncWorkOnRoot(root: FiberRoot) {
  // We disable time-slicing in some cases: if the work has been CPU-bound
  // for too long ("expired" work, to prevent starvation), or we're in
  // sync-updates-by-default mode.
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  root.finishedWork = root.current.alternate;
}

他们调用的renderSyncRoot | renderRootConcurrent函数中最主要的就是工作循环。

function renderRootSync(root: FiberRoot, lanes: Lanes) {
  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we'll continue where we left off.
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);
  }
  workLoopSync();
}
function renderRootSync(root: FiberRoot, lanes: Lanes) {
  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we'll continue where we left off.
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);
  }
  workLoopConcurrent();
}

工作循环

下面是这两种工作循环的代码。

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

workInProgess

workInProgress就是当前需要处理的 Fiber,可以调用下面的函数创建它:

// This is used to create an alternate fiber to do work on.
function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode
    );
  } else {
  }

  return workInProgress;
}

我们从 React 源码中的注释中看到这个函数使用了双缓冲池,如果current.alternate === null时才创建新的 Fiber,否则可以重用之前创建的 Fiber。

那么进入工作循环的第一个workInProgress是什么呢?

答案是:renderRootSyncrenderRootConcurrent中调用prepareFreshStack创建。

function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  workInProgress = rootWorkInProgress;
}

这里FiberRoot类型的root参数是哪里来的呢?

答案是:调用 react-dom package 的 Client API createRoot(domNode, options?)时创建的,reactDOMRoot._internalRoot就是FiberRootFiberRoot.current是 Fiber 节点,我们称它为 HostRoot。

两种工作循环的差别

它们的唯一区别是在调用performUnitOfWork前是否判断shouldYield()以便让出主线程,这个 API 是 scheduler package 提供的,它的精简代码如下:

function shouldYieldToHost() {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < frameInterval) {
    // The main thread has only been blocked for a really short amount of time;
    // smaller than a single frame. Don't yield yet.
    return false;
  }
  return true;
}

贴士:源码中本来还有更多判断,例如使用 Facebook 和 Chrome 合作的 API navigator.scheduling.isInputPending(),但是由于 React 目前默认没有开启这个功能,所以代码精简了。

如果占用主线程时间超出frameInterval,那么就需要让出主线程,frameInterval的初始值是 5ms。

performUnitOfWork

如果满足工作循环的判断,那么就会进入performUnitOfWork,下面是这个函数的精简版本:

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

这个函数非常好理解:

  • 调用beginWork
    • 如果没有返回下一个 Fiber,那么就调用completeUnitOfWork
    • 否则让workInProgress指向下一个 Fiber,进入下一次工作循环。

beginWork

我们暂时不深入了解这个函数,现在我们仅仅需要关注它的返回值,它始终返回workInProgress.child或者null,值得注意的是workInProgress.child也可能是null

completeUnitOfWork

如果 beginWork 返回 null,意味着这个分支已经没有需要处理的 Fiber 了,那么就可以完成当前这个 Fiber,然后可以接着处理它的兄弟节点,然后返回父节点。

下面是这个函数的精简版本:

function completeUnitOfWork(unitOfWork) {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork = unitOfWork;
  do {
    const next = completeWork(unitOfWork);
    if (next !== null) {
      // Completing this fiber spawned new work. Work on that next.
      workInProgress = next;
      return;
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }

    // Otherwise, return to the parent
    completedWork = completedWork.return;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);
}

渲染阶段结束

到这里渲染阶段就结束了,可以看到实际工作是在 beginWork 和 completeWork 中完成的,但是我们目前还没有深入了解这两个函数。

可能到这里我们有一个疑问,渲染阶段到底产出了什么呢?

答案是:生成了一个“全新”的 Fiber tree,之所以加引号,是因为并非所有的 Fiber 都是新创建的,可能是重用了之前的 Fiber,其中的 Fiber 有可能还标记了副作用。

FiberRootNode.finishedWork还指向了这个新的 Fiber tree,这在下一个阶段中有使用到。

什么是副作用

副作用这个词从字面上很难理解,React 文档中有一些关于副作用的解释。在计算机科学中,副作用表示对于函数外的变量,修改了参数等等,例如在事件处理函数中更改状态,发送 http 请求,导航到其他页面等等都是副作用。我们熟知的 Hook 还有类组件的一些生命周期方法都是副作用。

还记得我们之前提到渲染阶段必须是纯函数,不能有任何副作用,否则 UI 将不受控制,所以在渲染阶段只是将副作用标记在 Fiber 上,等进入提交阶段再执行副作用。

Fiber 对象中有一些属性就是专门为副作用设置的:

{
  // Effect
  flags: Flags,
  subtreeFlags: Flags,
  deletions: Array<Fiber> | null,

  // Singly linked list fast path to the next fiber with side-effects.
  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,
}

flags中就保存了副作用的 flag,例如Placement | Update | ChildDeletion,值得注意的是flags中可能保存了很多副作用。

原来的设计中,属性nexeEffect使得有副作用的 Fiber 可以串联成一个链,但是后来不再使用nextEffect | firstEffect | lastEffect,而是去遍历整颗树。

提交阶段

提交阶段可以拆分成下面几个子阶段:

  • before mutation 阶段
    对 host tree(例如 DOM 树)做出修改前,例如类组件的getSnapshotBeforeUpdate在这个阶段被调用。
  • mutation 阶段
    插入,修改,删除 DOM 节点等等。
  • layout 阶段
    修改 host tree 后,在浏览器进行绘制前,例如类组件的componentDidMount | componentDidUpdate在这个阶段被调用。

提交阶段主要包括在commitRoot函数中,它的精简版本如下:

function commitRoot(root: FiberRoot) {
  const finishedWork = root.finishedWork;
  // The commit phase is broken into several sub-phases. We do a separate pass
  // of the effect list for each phase: all mutation effects come before all
  // layout effects, and so on.

  // The first phase a "before mutation" phase. We use this phase to read the
  // state of the host tree right before we mutate it. This is where
  // getSnapshotBeforeUpdate is called.
  commitBeforeMutationEffects(root, finishedWork);

  // The next phase is the mutation phase, where we mutate the host tree.
  commitMutationEffects(root, finishedWork);

  // The work-in-progress tree is now the current tree. This must come after
  // the mutation phase, so that the previous tree is still current during
  // componentWillUnmount, but before the layout phase, so that the finished
  // work is current during componentDidMount/Update.
  root.current = finishedWork;

  // The next phase is the layout phase, where we call effects that read
  // the host tree after it's been mutated. The idiomatic use case for this is
  // layout, but class component lifecycles also fire here for legacy reasons.
  commitLayoutEffects(finishedWork, root, lanes);
}

到这里渲染和提交阶段就结束了。

文章来自个人专栏
React源码分析
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0