Fragment
Fragment 让你在不添加额外 DOM 节点的情况下对子元素进行分组。
核心概述
⚠️ 痛点: React 组件必须返回单个元素
- • JSX 要求组件必须有唯一的根元素
- • 使用 div 包裹会创建额外的 DOM 节点
- • 额外的 DOM 节点可能破坏 HTML 结构(如 table, ul)
- • 影响 CSS Grid 和 Flexbox 布局
✅ 解决方案: Fragment 无包裹分组
- • 不创建额外 DOM: Fragment 不会渲染真实节点
- • 支持简写:
<></>更简洁 - • 保持语义: 不破坏 HTML 结构
- • 支持 key: 列表中可使用带 key 的 Fragment
💡 心智模型: 透明包装纸
将 Fragment 想象成"透明包装纸":
- • 可以包裹多个物品: 允许返回多个子元素
- • 完全透明: 在 DOM 中不可见
- • 不占用空间: 不影响布局
- • 可以标记: 支持 key 属性标识
技术规格
语法
// 完整形式
<React.Fragment>
{/* 子元素 */}
</React.Fragment>
// 简写形式
<Fragment>
{/* 子元素 */}
</Fragment>
// 带有 key 的 Fragment
<React.Fragment key={uniqueKey}>
{/* 子元素 */}
</React.Fragment>限制
| 特性 | 支持情况 |
|---|---|
key 属性 | ✅ 支持(仅完整形式) |
| 其他属性(className, style 等) | ❌ 不支持 |
简写形式 <></> | ⚠️ 不支持任何属性 |
实战演练
示例 1: 基础用法
// ❌ 错误: 不能返回多个元素
function Wrong() {
return (
<h1>标题</h1>
<p>段落</p>
);
}
// ⚠️ 可行但创建额外 DOM
function WrappedWithDiv() {
return (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
}
// ✅ 推荐: 使用 Fragment 简写
function Correct() {
return (
<Fragment>
<h1>标题</h1>
<p>段落</p>
</Fragment>
);
}
// ✅ 完整形式
function WithKey({ items }: { items: Item[] }) {
return (
<React.Fragment key={items[0].id}>
<dt>{items[0].term}</dt>
<dd>{items[0].description}</dd>
</React.Fragment>
);
}示例 2: 保持 HTML 语义
// ❌ 错误: div 不能作为 table 的直接子元素
function BrokenTable() {
return (
<table>
<div>
<tr><td>行 1</td></tr>
<tr><td>行 2</td></tr>
</div>
</table>
);
}
// ✅ 正确: 使用 Fragment
function CorrectTable() {
return (
<table>
<tbody>
<Fragment>
<tr><td>行 1</td></tr>
<tr><td>行 2</td></tr>
</Fragment>
</tbody>
</table>
);
}
// ✅ 列表渲染使用带 key 的 Fragment
function Glossary({ items }: { items: { id: string; term: string; description: string }[] }) {
return (
<dl>
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}示例 3: 条件渲染多个元素
function UserInfo({ user }: { user: User | null }) {
return (
<Fragment>
{user ? (
<Fragment>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={() => editProfile(user)}>编辑</button>
</Fragment>
) : (
<Fragment>
<h2>访客</h2>
<p>请登录以查看更多信息</p>
<button onClick={login}>登录</button>
<button onClick={register}>注册</button>
</Fragment>
)}
</Fragment>
);
}
// 优点:
// 1. 避免创建额外的 DOM 节点
// 2. 保持代码结构清晰
// 3. CSS Grid/Flexbox 布局不受影响避坑指南
❌ 错误 1: 给简写 Fragment 添加属性
// ❌ 错误: 简写形式不支持属性
function ErrorExample() {
return (
<Fragment>
<div>Item 1</div>
<div>Item 2</div>
</Fragment>
);
}
// 问题:
// 1. TypeScript 会报错
// 2. Babel 会编译失败
// 3. React 无法识别属性✅ 正确 1: 使用完整形式添加属性
// ✅ 正确: 使用完整形式
function CorrectExample({ items }) {
return (
<React.Fragment key={items[0].id}>
<div>Item 1</div>
<div>Item 2</div>
</React.Fragment>
);
}
// 优点:
// 1. 可以添加 key 属性
// 2. 语义更明确
// 3. TypeScript 不会报错❌ 错误 2: 过度使用 Fragment
// ❌ 错误: 单个元素不需要 Fragment
function OverusedFragment() {
return (
<Fragment>
<div>单个元素</div>
</Fragment>
);
}
// 问题:
// 1. 增加代码复杂度
// 2. 没有任何好处
// 3. 降低可读性✅ 正确 2: 只在需要时使用 Fragment
// ✅ 正确: 单个元素直接返回
function SingleElement() {
return <div>单个元素</div>;
}
// ✅ 正确: 多个元素使用 Fragment
function MultipleElements() {
return (
<Fragment>
<div>元素 1</div>
<div>元素 2</div>
</Fragment>
);
}
// 优点:
// 1. 代码简洁
// 2. 语义清晰
// 3. 性能最优❌ 错误 3: 期望 Fragment 支持样式
// ❌ 错误: Fragment 不支持 className
function ErrorExample() {
return (
<React.Fragment className="container">
<div>Item 1</div>
<div>Item 2</div>
</React.Fragment>
);
}
// 问题:
// 1. className 会被忽略
// 2. 样式不会生效
// 3. 容易产生 bug✅ 正确 3: 使用 div 添加样式
// ✅ 正确: 需要样式时使用 div
function StyledWrapper() {
return (
<div className="container flex gap-4">
<div>Item 1</div>
<div>Item 2</div>
</div>
);
}
// ✅ 正确: Fragment 用于无样式包裹
function UnstyledGroup() {
return (
<Fragment>
<div>Item 1</div>
<div>Item 2</div>
</Fragment>
);
}
// 使用建议:
// - 需要样式/事件监听 → 使用 div
// - 只需分组 → 使用 Fragment最佳实践
1. Fragment vs 其他方案对比
| 方案 | 额外 DOM | 支持属性 | 适用场景 |
|---|---|---|---|
Fragment | ❌ 无 | 仅 key | ✅ 大多数场景 |
<div> | ✅ 有 | 全部 | 需要样式/事件 |
| 数组 | ❌ 无 | 无 | ⚠️ 不推荐(需要 key) |
2. 常见使用场景
// 场景 1: 表格行列
function TableRow() {
return (
<tr>
<Fragment>
<td>单元格 1</td>
<td>单元格 2</td>
<td>单元格 3</td>
</Fragment>
</tr>
);
}
// 场景 2: 定义列表
function DefinitionList({ items }) {
return (
<dl>
{items.map(item => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
);
}
// 场景 3: CSS Flexbox 容器
function FlexContainer({ children }) {
return (
<div style={{ display: 'flex' }}>
{/* Fragment 不会影响 Flexbox 布局 */}
{children}
</div>
);
}
// 场景 4: 条件渲染多个元素
function Conditional({ showDetails }) {
return (
<Fragment>
<h1>标题</h1>
{showDetails && (
<Fragment>
<p>详细信息 1</p>
<p>详细信息 2</p>
</Fragment>
)}
</Fragment>
);
}3. 性能考虑
// Fragment vs div 性能对比
// 使用 div - 创建额外 DOM 节点
function WithDiv({ items }) {
return (
<div>
{items.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// 使用 Fragment - 无额外 DOM
function WithFragment({ items }) {
return (
<Fragment>
{items.map(item => (
<div key={item.id}>{item.name}</div>
))}
</Fragment>
);
}
// 性能差异:
// 1. 内存使用: Fragment < div
// 2. 渲染时间: Fragment ≈ div(差异极小)
// 3. DOM 大小: Fragment 更小
// 建议:
// - 大型列表优先使用 Fragment
// - 性能敏感场景使用 Fragment
// - 需要样式时使用 div相关链接
- Component API - 类组件基础
- Suspense API - 代码分割和数据获取
- StrictMode API - 检测潜在问题
- 描述 UI - JSX 和组件基础