实现 React Fragment 节点渲染

基于文章 实现 React 文本节点渲染

本文将从三方面介绍如何渲染 Fragment 节点。

  1. 子节点 <>
  2. 根节点 <>
  3. 标签 <Fragment>

子节点 <>

js 复制代码
let fragment1: any = (
    <>
        <h3>1</h3>
        <h4>2</h4>
    </>
)
const jsx = (
  <div className="box border">
    <h1 className="border">omg</h1>
    <h2>react</h2>
    omg2
    {fragment1}
  </div>
)

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);

Render 阶段

BeginWork 阶段

beginWork 函数增加 Fragment 点的 case。

js 复制代码
function updateHostFragment(current: Fiber | null, workInProgress: Fiber) {
  const nextChildren = workInProgress.pendingProps.children;
  reconcileChildren(current, workInProgress, nextChildren);
  return workInProgress.child;
}

// 1. 处理当前 fiber,因为不同组件对应的 fiber 处理方式不同,
// 2. 返回子节点 child
export function beginWork(
  current: Fiber | null,
  workInProgress: Fiber
): Fiber | null {
  switch (workInProgress.tag) {
    // 根节点
    case HostRoot:
      return updateHostRoot(current, workInProgress);
    // 原生标签,div、span...
    case HostComponent:
      return updateHostComponent(current, workInProgress);
    // 文本节点
    case HostText:
      return updateHostText(current, workInProgress);
    // Fragment
    case Fragment:
      return updateHostFragment(current, workInProgress);
  }
  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      "React. Please file an issue."
  );
}

修改 createFiberFromTypeAndProps 函数,增加 Fragment 的 case。

js 复制代码
// 根据 TypeAndProps 创建fiber
export function createFiberFromTypeAndProps(
  type: any,
  key: null | string,
  pendingProps: any
) {
  let fiberTag: WorkTag = IndeterminateComponent;

  if (isStr(type)) {
    // 原生标签
    fiberTag = HostComponent;
  } else if (type === REACT_FRAGMENT_TYPE) {
    fiberTag = Fragment;
  }
  const fiber = createFiber(fiberTag, pendingProps, key);
  fiber.elementType = type;
  fiber.type = type;
  return fiber;
}

CompleteWork 阶段

completeWork 函数增加 Fragment 的 case。

js 复制代码
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber
): Fiber | null {
  const newProps = workInProgress.pendingProps;
  switch (workInProgress.tag) {
    case Fragment:
    case HostRoot: {
      return null;
    }
    case HostComponent: {
      // 原生标签,type 是标签名
      const { type } = workInProgress;
        // 1. 创建真实 DOM
        const instance = document.createElement(type);
        // 2. 初始化 DOM 属性
        finalizeInitialChildren(instance, newProps);
        // 3. 把子 dom 挂载到父 dom 上
        appendAllChildren(instance, workInProgress);
        workInProgress.stateNode = instance;
      return null;
    }
    case HostText: {
      workInProgress.stateNode = document.createTextNode(newProps);
      return null;
    }
  }

  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      "React. Please file an issue."
  );
}

appendAllChildren 需要做相应的修改。

js 复制代码
// fiber.stateNode是DOM节点
export function isHost(fiber: Fiber): boolean {
  return fiber.tag === HostComponent || fiber.tag === HostText;
}

function appendAllChildren(parent: Element, workInProgress: Fiber) {
  let nodeFiber = workInProgress.child; // 链表结构
  while (nodeFiber !== null) {
    if (isHost(nodeFiber)) {
      parent.appendChild(nodeFiber.stateNode); // nodeFiber.stateNode是DOM节点
    } else if (nodeFiber.child !== null) {
      // 如果 node 这个 fiber 本身不直接对应 DOM 节点,那么就往上找它的子节点,直到找到有真实的 DOM 节点的 fiber 为止
      nodeFiber = nodeFiber.child;
      continue;
    }
    if (nodeFiber === workInProgress) {
      return;
    }
    // 如果 nodeFiber 没有兄弟节点了,那么就往上找它的父节点
    while (nodeFiber.sibling === null) {
      if (nodeFiber.return === null || nodeFiber.return === workInProgress) {
        return;
      }

      nodeFiber = nodeFiber.return;
    }

    nodeFiber = nodeFiber.sibling;
  }
}

根节点 <>

js 复制代码
let fragment1: any = (
    <>
        <h3>1</h3>
        <h4>2</h4>
    </>
)

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(fragment1);

Commit 阶段

commitPlacement 增加 Fragment 的 case。

js 复制代码
function commitPlacement(finishedWork: Fiber) {
  if (finishedWork.stateNode && isHost(finishedWork)) {
    // finishedWork 是有 dom 节点
    const domNode = finishedWork.stateNode
    // 找 domNode 的父 DOM 节点对应的 fiber
    const parentFiber = getHostParentFiber(finishedWork);

    let parentDom = parentFiber.stateNode;

    if (parentDom.containerInfo) {
      // HostRoot
      parentDom = parentDom.containerInfo;
    }

    parentDom.appendChild(domNode)
  } else {
    // Fragment
    let kid = finishedWork.child;
    while (kid !== null) {
      commitPlacement(kid);
      kid = kid.sibling;
    }
  }
}

标签 <Fragment>

js 复制代码
let fragment1: any = (
    <Fragment key='sy'>
        <h3>1</h3>
        <h4>2</h4>
    </Fragment>
)

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(fragment1);

实现比较简单,就两句话。

js 复制代码
export const REACT_FRAGMENT_TYPE: symbol = Symbol.for("react.fragment");

export { REACT_FRAGMENT_TYPE as Fragment } from "shared/ReactSymbols";
相关推荐
DoraBigHead5 分钟前
this 的前世今生:谁在叫我,我听谁的
前端·javascript·面试
蓝婷儿43 分钟前
每天一个前端小知识 Day 28 - Web Workers / 多线程模型在前端中的应用实践
前端
琹箐43 分钟前
Ant ASpin自定义 indicator 报错
前端·javascript·typescript
小小小小小惠1 小时前
Responsetype blob会把接口接收的二进制文件转换成blob格式
前端·javascript
爱电摇的小码农1 小时前
【深度探究系列(5)】:前端开发打怪升级指南:从踩坑到封神的解决方案手册
前端·javascript·css·vue.js·node.js·html5·xss
kymjs张涛1 小时前
零一开源|前沿技术周报 #7
android·前端·ios
爱编程的喵1 小时前
React入门实战:从静态渲染到动态状态管理
前端·javascript
Tttian6221 小时前
npm init vue@latestnpm error code ETIMEDOUT
前端·vue.js·npm
患得患失9492 小时前
【前端】【组件库开发】【原理】【无框架开发】现代网页弹窗开发指南:从基础到优化
前端
AliciaIr2 小时前
深入React事件机制:解密“合成事件”与“事件委托”的底层奥秘
javascript·react.js