🧩 React Fragment 的底层原理:为什么它能“包裹却不包裹”?

209 阅读3分钟

🧩 React Fragment 的底层原理:为什么它能“包裹却不包裹”?

你是否好奇,为什么 <Fragment> 可以像个“幽灵容器”一样不渲染任何 HTML,却又能同时包裹多个元素?本篇文章将带你一探 React Fragment 的底层奥秘!


🌟 前言:为什么需要 Fragment?

在早期的 React 中,我们写组件时遇到一个经典问题:

// ❌ 错误:JSX 必须有一个根节点
return (
  <h1>标题</h1>
  <p>段落</p>
);

JSX 只能返回一个根节点,为了规避报错,我们常常不得不这样写:

// ✅ 有效但冗余的 div
return (
  <div>
    <h1>标题</h1>
    <p>段落</p>
  </div>
);

但是,这种多余的 <div> 会污染 DOM 结构,不利于样式控制和性能优化。为此,React 引入了 Fragment ——一个“看得见但摸不着”的神秘容器。


👻 什么是 Fragment?

<Fragment> 是 React 提供的一个特殊组件,它 允许你返回多个子元素而不额外包裹 DOM 元素

import React, { Fragment } from 'react';

return (
  <Fragment>
    <h1>标题</h1>
    <p>段落</p>
  </Fragment>
);

或者使用更简洁的写法(空标签):

<>
  <h1>标题</h1>
  <p>段落</p>
</>

渲染结果👇

<h1>标题</h1>
<p>段落</p>

没有任何多余的 div 或 span!


🔍 Fragment 背后的工作机制

1. 虚拟 DOM 层:Fragment 是一个“占位节点”

在 React 的虚拟 DOM 中,Fragment 是一种 特殊类型的 Fiber 节点,它的 type 并不是 'div',而是一个内部标识:

type FiberNode = {
  type: string | function | Symbol;
  tag: number;
  ...
}

当使用 <Fragment> 时,React 会生成一个 REACT_FRAGMENT_TYPE 类型的 Fiber:

const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');

这一类型告诉 React:“这是个 Fragment,不要生成真实 DOM 元素。”


2. 渲染阶段:跳过 Fragment 本身

ReactDOM 的渲染过程中,Fragment Fiber 的作用仅仅是用来 组织它的子节点

  • Fragment 自己不会被转化为 DOM 节点。
  • React 会跳过它,直接递归处理它的子节点。

底层逻辑类似下面伪代码:

if (fiber.type === REACT_FRAGMENT_TYPE) {
  // 不创建 DOM 元素,跳过自己
  return renderChildren(fiber.children);
}

3. 为什么可以写成 <> </>

这是 React 对 Fragment 的语法糖,等价于:

<>内容</> === <React.Fragment>内容</React.Fragment>

这在 Babel 编译阶段就会转换成:

React.createElement(React.Fragment, null, ...children);

📦 Fragment 的高级玩法

1. keyed Fragment(带 key 的 Fragment)

当你在列表中返回 Fragment 时,可以也应该加上 key

data.map(item => (
  <React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.description}</dd>
  </React.Fragment>
));

带 key 的 Fragment 会被 React 正常地用于 Diff 过程,性能更优,避免不必要的重渲染


2. Fragment vs div:性能对比

对比项Fragmentdiv
是否生成 DOM❌ 不生成✅ 会生成
影响样式✅ 无影响❌ 可能影响布局
性能✅ 更快(跳过生成)⛔️ 有额外创建成本
使用场景✅ 结构包装、列表⛔️ 用于样式布局再考虑

🧠 总结一下

问题解答
Fragment 会不会生成 DOM?不会。
Fragment 是怎么实现的?通过虚拟 DOM 中特殊的 REACT_FRAGMENT_TYPE 类型。
它存在的意义?不污染 DOM,又能返回多个元素。
空标签 <> </> 是什么?React.Fragment 的语法糖。
可以加 key 吗?可以,尤其在列表中推荐加。

✨ 结尾彩蛋:Fragment 在源码中的位置

你可以在 React 的源码 ReactElement.js 里找到这段:

export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');

而在调和阶段(reconciler)中,也有对 REACT_FRAGMENT_TYPE 的判断分支,用于正确跳过 DOM 创建,专心处理子节点。