好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

React18系列commit从0实现源码解析

正文

本系列是讲述从0开始实现一个react18的基本版本。由于 React 源码通过 Mono-repo 管理仓库,我们也是用 pnpm 提供的 workspaces 来管理我们的代码仓库,打包我们使用 rollup 进行打包。

仓库地址

本章节主要是讲解我们如何将上一节得到的fiberNode树,渲染到页面中。 React 中commit分为下面三个阶段,这节我们讲解简单的commit阶段:

beforeMutation阶段 mutation阶段 layout阶段

这节主要讲解commit其中的 mutation placement 阶段:

我们经常把 Renderer 工作的阶段被称为 commit 阶段。在 commit 阶段,会将我们上一讲中生成的各种flags提交( commit )到宿主环境UI中。我们前端通常使用的是 ReactDOM 进行处理。这节我们简单的实现了一个 ReactDom 包,看看是如何把调和和浏览器环境连起来的。

桥梁

在 React 中, react-reconciler 和 react-dom 是2个不同的包, react-dom 主要是提供浏览器宿主相关的方法。回想我们每次在使用 react 开发项目的过程中,通过下方代码调用:

ReactDom.createRoot(root).render(<App />)

所以需要提供 createRoot 方法。返回一个 render 方法,接收 ReactElement 元素。

export function createRoot(container: Container) {
  const root = createContainer(container);
  return {
    render(element: ReactElementType) {
      updateContainer(element, root);
    },
  };
}

内部通过 createContainer 和 updateContainer 建立2个包的连接。具体的流程可以查看我们之前的章节。

commit Effect

在上一节中,我们在 workLoop 执行完后后得到了一个带有标记的 wip fiber tree (如下图所示), 在commit阶段,我们要通过这个fiber树将内容渲染到屏幕中。

commitRoot执行

将最后生成的 finishedWork 传递给 commitRoot , 然后根据顶部的 flags 和 subtreeFlags 来判断是否有渲染节点:

function commitRoot(root: FiberRootNode) {
  const finishedWork = root.finishedWork;
  if (finishedWork === null) {
    return;
  }
  // 重置
  root.finishedWork = null;
  // 判断是否存在子阶段需要执行的操作
  const subtreeHasEffect =
    (finishedWork.subtreeFlags & MutationMask) !== NoFlags; // 子节点是否有更新
  const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags; // 根节点是否更新
  if (subtreeHasEffect || rootHasEffect) {
    // beforeMutation
    // mutation Placement
    commitMutationEffects(finishedWork);
    root.current = finishedWork;
    // layout
  } else {
    root.current = finishedWork;
  }
}

commitMutationEffects执行

我们知道div对应的 fiberNode 是标有 flags = placement 的,所以在 hostFiberNode 中的 subtreeHasEffect 是有值的,所以会走到 commitMutationEffects 分支。那它内部到底是有什么作用呢?

commitMutationEffects执行 的主要作用就是找到对应flags的fiberNode, 并执行相应的Dom操作。

我们需要向下遍历找到最底部的 subtreeFlags 不为0的fiberNode的 子fiberNode 节点 由于父节点的 subtreeFlags 存在,不代表 child 对应的 flags 存在,可能是 child 对应的 sibling 节点的 flags 存在 所以在找到对应的 subtreeFlags 的 child 的fiberNode后,需要在向上遍历,查找对应的 sibling 节点。

commitMutationEffectsOnFibers

对每一个 fiberNode 我们会执行commit Effects的操作。

const commitMutationEffectsOnFibers = (finishedWork: FiberNode) => {
  const flags = finishedWork.flags;
  if ((flags & Placement) !== NoFlags) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  // flags update
  // flags childDeletion
};
const commitPlacement = (finishWork: FiberNode) => {
  if (__DEV__) {
    console.warn("执行commitPlacement操作", finishWork);
  }
  // parentDom 插入 finishWork对应的dom
  // 1. 找到parentDom
  const hostParent = getHostParent(finishWork);
  if (hostParent !== null) {
    appendPlacementNodeIntoContainer(finishWork, hostParent);
  }
};

其中有一个 getHostParent 是获取到对应父 Container (容器元素),对应浏览器环境就是 Dom ,

getHostParent执行

找父contianer容器,向上递归 主要是分为2大类。第一类是对应 HostComponent (类似 <div> ), 第二类就是根节点( #root ), 他们分别对应不同的位置。

function getHostParent(fiber: FiberNode): Container | null {
  let parent = fiber.return;
  while (parent) {
    const parentTag = parent.tag;
    // HostComponent  HostRoot
    if (parentTag === HostComponent) {
      return parent.stateNode as Container;
    }
    if (parentTag === HostRoot) {
      return (parent.stateNode as FiberRootNode).container;
    }
    parent = parent.return;
  }
  if (__DEV__) {
    console.warn("未找到HostParent");
  }
  return null;
}

appendPlacementNodeIntoContainer执行

当我们上一步找到父container后,接下来就是要把自身的 stateNode 插入到父container中,然后渲染的界面上。

appendPlacementNodeIntoContainer 这个函数接受2个参数,第一个是当前 fiberNode , 第二个父container。想想什么样的 fiberNode 需要渲染到屏幕中。目前demo层级来看,只有下面2个节点需要展示到屏幕中。

HostComponent HostText

所以 appendPlacementNodeIntoContainer 需要向下找到对应的节点。然后插入到contaienr中

function appendPlacementNodeIntoContainer(
  finishedWork: FiberNode,
  hostParent: Container
) {
  // fiber Host
  if (finishedWork.tag === HostComponent || finishedWork.tag === HostText) {
    appendChildToContainer(hostParent, finishedWork.stateNode);
    return;
  }
  const child = finishedWork.child;
  if (child !== null) {
    appendPlacementNodeIntoContainer(child, hostParent);
    let sibling = child.sibling;
    while (sibling !== null) {
      appendPlacementNodeIntoContainer(sibling, hostParent);
      sibling = sibling.sibling;
    }
  }
  return null;
}

例子:

我们通过一个特定的例子,来讲解 commit mutation 中具体的执行过程。比如:我们有如下的结构,更新的 flags 标记如下:

commitMutationEffects 会找到 subtreeFlags 值不为0的顶层 fiberNode ,然后开始向下遍历,直到找到 world fiberNode

递归向上的执行 commitMutationEffectsOnFibers ,先自己,然后 sibling , 然后 return 。

commitMutationEffectsOnFibers : 例如 world fiberNode ,自身 flags = 1 ,然后会通过 getHostParent 找到类型为 HostComponent 的 div fiberNode , 然后把自己的 stateNode 对应的dom, 通过 appendChild 插入到 div fiberNode 的 stateNode 中。

这样一直向上,最终就会把 div fiberNode 对应的dom元素, append 到 root 中。

以上就是React18系列commit从0实现源码解析的详细内容,更多关于React18系列commit源码的资料请关注其它相关文章!

原文地址:https://juejin.cn/post/7183611473715789884

查看更多关于React18系列commit从0实现源码解析的详细内容...

  阅读:33次