实现 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";
相关推荐
Predestination王瀞潞23 分钟前
WEB前端技术基础(第四章:JavaScript-网页动态交互语言)
前端·javascript·交互
静西子2 小时前
Vue3路由
前端·javascript·vue.js
J总裁的小芒果2 小时前
vue3 全局定义动态样式
前端·javascript·vue.js
whalekv2 小时前
10月25日
前端·javascript·vue.js
万邦科技Lafite4 小时前
京东按图搜索京东商品(拍立淘) API (.jd.item_search_img)快速抓取数据
开发语言·前端·数据库·python·电商开放平台·京东开放平台
一只小透明啊啊啊啊7 小时前
Java Web 开发的核心组件:Servlet, JSP,Filter,Listener
java·前端·servlet
你的人类朋友7 小时前
设计模式有哪几类?
前端·后端·设计模式
Yeats_Liao8 小时前
Go Web 编程快速入门 10 - 数据库集成与ORM:连接池、查询优化与事务管理
前端·数据库·后端·golang
啃火龙果的兔子8 小时前
前端八股文react篇
前端·react.js·前端框架
打小就很皮...8 小时前
React 实现 i18next 中英文切换集成
前端·react.js·i18next