深入理解 React Fragment

81 阅读5分钟

React 作为现代前端开发的主流框架之一,提供了许多便捷的工具和 API 来提升开发体验和代码质量。Fragment(片段)就是其中一个非常实用但又常被初学者忽略的特性。

什么是 React Fragment

React Fragment 是 React 提供的一个用于包裹一组子元素的组件。它不会在 DOM 中额外创建节点,只是作为一个“虚拟容器”存在于 React 的虚拟 DOM(Virtual DOM)中。

简单来说,Fragment 允许你在不引入额外 DOM 元素的情况下,返回多个子元素。


为什么需要 Fragment

在 React 里,组件返回的内容得有一个根元素把它们包起来,要是直接返回多个零散的元素,程序就会报错,这就好比写作文得有个大标题来把段落串起来,让结构清晰。

在以前大家常用 <div> 当这个根,可它会在页面的真实 DOM 里多塞一个没用的 <div> 节点。比如说做表格的时候,想在 <tr> 里放几个 <td>,要是用 <div> 包起来,就会把表格原本的结构和语义给破坏了,还会让 DOM 变得复杂,影响样式和性能。

而 Fragment 就是专门用来解决这个问题的。它就像一个“隐形容器”,能把多个元素包起来,让组件满足有一个根的要求,还不会在真实 DOM 里多添加节点 。这样一来,既让组件的结构合法,又能让页面的 DOM 保持简洁干净,不管是做表格、列表这类对结构有要求的东西,还是给普通组件分组,用它都能让代码更合理 。

Fragment 的基本用法

1. 标准写法

import React, { Fragment } from 'react';

function Demo() {
  return (
    <Fragment>
      <h1>标题</h1>
      <p>内容</p>
    </Fragment>
  );
}
  1. 从 react 库中导入 React 和 Fragment :import React, { Fragment } from 'react'; ,Fragment 是 React 提供的用于分组多个元素,且不会在 DOM 中额外创建节点的组件 。

  2. 用 Fragment 优雅包裹组件内多个子元素,优化 DOM 结构

2. 简写语法

也可用更简洁的 <> 和 </> 语法替代 <Fragment> 标签写法(若不需要传递 key 等特殊属性时)

function Demo() {
  return (
    <>
      <h1>标题</h1>
      <p>内容</p>
    </>
  );
}

这两种写法完全等价,简写更常用。


Fragment 的主要特点

  1. 它像个 “透明的容器”,能把多个元素包起来,满足组件必须有一个根元素的要求,却不会在页面的真实结构里留下任何多余的标签,让 DOM 保持干净简洁。

  2. 它能保住 HTML 原有的语义和结构,比如在表格、列表这类对格式有严格要求的地方,不会像用普通容器那样破坏原本的合理结构。

  3. 用法灵活又简单,既可以用简短的语法快速包裹元素,也能在需要的时候添加关键标识(key),兼顾了便捷性和特殊场景的需求。

Fragment 的底层原理

从底层视角来看,React Fragment 的实现依赖于 React 元素模型和协调算法的设计,主要原理可概括为以下几点:

1. 特殊 React 元素类型

在 React 内部,每个 JSX 标签都会被编译为 React.createElement 调用,生成一个描述 UI 的对象(React 元素)。
Fragment 本质上是一种特殊类型的 React 元素,其 type 属性被标记为 REACT_FRAGMENT_TYPE(Symbol 或数字常量)。
例如:

<>
  <ChildA />
  <ChildB />
</>

// 编译后
React.createElement(React.Fragment, null, 
  React.createElement(ChildA, null),
  React.createElement(ChildB, null)
);

2. 跳过 DOM 渲染

当 React 协调器(Reconciler)处理到 Fragment 类型的元素时,会直接渲染其子节点,而不会在 DOM 中创建对应的容器节点。
对比普通元素(如 <div>)和 Fragment 的渲染流程:

  • 普通元素:创建 DOM 节点 → 插入子节点
  • Fragment:直接插入子节点(跳过创建容器节点)

3. 保持子节点连续性

Fragment 的子节点在 DOM 中是连续的,没有额外的父节点分隔。
例如:

// Fragment 包裹两个子节点
<>
  <p>第一个子节点</p>
  <p>第二个子节点</p>
</>

// 渲染后的 DOM
<p>第一个子节点</p>
<p>第二个子节点</p>

这种连续性对 CSS 选择器、事件冒泡等机制至关重要。

4. 支持 key 属性

当使用完整语法 <React.Fragment> 时,可以传递 key 属性(短语法 <>...</> 不支持)。
key 用于帮助 React 识别哪些元素发生了变化,在列表渲染等场景中尤为重要:

{items.map(item => (
  <React.Fragment key={item.id}>
    <dt>{item.name}</dt>
    <dd>{item.value}</dd>
  </React.Fragment>
))}

常见使用场景

1. 组件返回多个元素

function Header() {
  // 使用 Fragment 包裹多个同级元素
  return (
    <>
      <h1>标题</h1>
      <p>副标题</p>
    </>
  );
}

// 渲染结果(无额外容器):
// <h1>标题</h1>
// <p>副标题</p>

思路
React 要求组件必须返回单个根元素,但直接写多个元素会报错。使用 Fragment 作为 “虚拟根”,既满足语法要求,又不会在 DOM 中生成额外的 <div>

2. 表格结构中的行

function TableRow() {
  return (
    <tr>
      {/* 避免使用 <div> 破坏表格结构 */}
      <>
        <td>第一列</td>
        <td>第二列</td>
      </>
    </tr>
  );
}

// 正确渲染结果:
// <tr>
//   <td>第一列</td>
//   <td>第二列</td>
// </tr>

思路
HTML 表格对结构严格(<tr> 内只能直接放 <td>),使用 <div> 包裹会破坏表格语义。Fragment 允许你合法分组 <td>,同时保持表格的正确结构。

3. 列表循环中的多元素分组

function ItemList({ items }) {
  return (
    <ul>
      {items.map(item => (
        // 带 key 的 Fragment 包裹多个元素
        <React.Fragment key={item.id}>
          <li>{item.name}</li>
          <li className="subitem">{item.desc}</li>
        </React.Fragment>
      ))}
    </ul>
  );
}

思路
当需要为每个列表项渲染多个关联元素时(如主项 + 子项),使用带 key 的 Fragment 分组。这既帮助 React 识别元素变化,又不会在 DOM 中创建多余的 <div> 包裹列表项。

小结

React Fragment 提供了一种无需额外 DOM 节点即可组织子元素的优雅方式,特别适用于维护表格结构、列表语义或避免干扰 CSS 布局的场景。作为现代 React 开发的基础工具,它能有效减少冗余代码,提升组件的可读性与性能,是编写简洁、高效 UI 代码的必备技巧。

参考资料: