实现 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";
相关推荐
tianzhiyi1989sq1 小时前
Vue3 Composition API
前端·javascript·vue.js
今禾1 小时前
Zustand状态管理(上):现代React应用的轻量级状态解决方案
前端·react.js·前端框架
用户2519162427111 小时前
Canvas之图形变换
前端·javascript·canvas
今禾2 小时前
Zustand状态管理(下):从基础到高级应用
前端·react.js·前端框架
gnip2 小时前
js模拟重载
前端·javascript
Naturean2 小时前
Web前端开发基础知识之查漏补缺
前端
curdcv_po2 小时前
🔥 3D开发,自定义几何体 和 添加纹理
前端
单身汪v2 小时前
告别混乱:前端时间与时区实用指南
前端·javascript
鹏程十八少2 小时前
2. Android 深度剖析LeakCanary:从原理到实践的全方位指南
前端
我是ed2 小时前
# cocos2 场景跳转传参
前端