🧩 React Fragment 的底层原理:为什么它能"包裹却不包裹"?
你是否好奇,为什么
<Fragment>
可以像个"幽灵容器"一样不渲染任何 HTML,却又能同时包裹多个元素?本篇文章将带你一探 React Fragment 的底层奥秘!
🌟 前言:为什么需要 Fragment?
在早期的 React 中,我们写组件时遇到一个经典问题:
javascript
// ❌ 错误:JSX 必须有一个根节点
return (
<h1>标题</h1>
<p>段落</p>
);
JSX 只能返回一个根节点,为了规避报错,我们常常不得不这样写:
javascript
// ✅ 有效但冗余的 div
return (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
但是,这种多余的 <div>
会污染 DOM 结构,不利于样式控制和性能优化。为此,React 引入了 Fragment ------一个"看得见但摸不着"的神秘容器。
👻 什么是 Fragment?
<Fragment>
是 React 提供的一个特殊组件,它 允许你返回多个子元素而不额外包裹 DOM 元素。
javascript
import React, { Fragment } from 'react';
return (
<Fragment>
<h1>标题</h1>
<p>段落</p>
</Fragment>
);
或者使用更简洁的写法(空标签):
xml
<>
<h1>标题</h1>
<p>段落</p>
</>
渲染结果👇
css
<h1>标题</h1>
<p>段落</p>
没有任何多余的 div 或 span!
🔍 Fragment 背后的工作机制
1. 虚拟 DOM 层:Fragment 是一个"占位节点"
在 React 的虚拟 DOM 中,Fragment 是一种 特殊类型的 Fiber 节点 ,它的 type
并不是 'div'
,而是一个内部标识:
typescript
type FiberNode = {
type: string | function | Symbol;
tag: number;
...
}
当使用 <Fragment>
时,React 会生成一个 REACT_FRAGMENT_TYPE
类型的 Fiber:
ini
const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
这一类型告诉 React:"这是个 Fragment,不要生成真实 DOM 元素。"
2. 渲染阶段:跳过 Fragment 本身
在 ReactDOM
的渲染过程中,Fragment Fiber 的作用仅仅是用来 组织它的子节点。
- Fragment 自己不会被转化为 DOM 节点。
- React 会跳过它,直接递归处理它的子节点。
底层逻辑类似下面伪代码:
scss
if (fiber.type === REACT_FRAGMENT_TYPE) {
// 不创建 DOM 元素,跳过自己
return renderChildren(fiber.children);
}
3. 为什么可以写成 <> </>
?
这是 React 对 Fragment 的语法糖,等价于:
xml
<>内容</> === <React.Fragment>内容</React.Fragment>
这在 Babel 编译阶段就会转换成:
csharp
React.createElement(React.Fragment, null, ...children);
📦 Fragment 的高级玩法
1. keyed Fragment(带 key 的 Fragment)
当你在列表中返回 Fragment 时,可以也应该加上 key:
javascript
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:性能对比
对比项 | Fragment | div |
---|---|---|
是否生成 DOM | ❌ 不生成 | ✅ 会生成 |
影响样式 | ✅ 无影响 | ❌ 可能影响布局 |
性能 | ✅ 更快(跳过生成) | ⛔️ 有额外创建成本 |
使用场景 | ✅ 结构包装、列表 | ⛔️ 用于样式布局再考虑 |
🧠 总结一下
问题 | 解答 |
---|---|
Fragment 会不会生成 DOM? | 不会。 |
Fragment 是怎么实现的? | 通过虚拟 DOM 中特殊的 REACT_FRAGMENT_TYPE 类型。 |
它存在的意义? | 不污染 DOM,又能返回多个元素。 |
空标签 <> </> 是什么? |
React.Fragment 的语法糖。 |
可以加 key 吗? | 可以,尤其在列表中推荐加。 |
✨ 结尾彩蛋:Fragment 在源码中的位置
你可以在 React 的源码 ReactElement.js
里找到这段:
ini
export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
而在调和阶段(reconciler)中,也有对 REACT_FRAGMENT_TYPE
的判断分支,用于正确跳过 DOM 创建,专心处理子节点。