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

🧩 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 创建,专心处理子节点。

相关推荐
赛博守夜人11 小时前
AI安全攻防与治理之十四:AI 安全测试不能脱离传统安全,Web、API、移动端与模型攻击面的组合拳
前端·人工智能·安全
开开心心_Every11 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
networknomad11 小时前
OpenReplay:自己搭的会话回放工具,给前端排错用的
前端·其他
缓慢更新12 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
陈随易12 小时前
AI 运维服务器,安全又可靠的方法
前端·后端·程序员
风骏时光牛马13 小时前
AI稳定性治理体系构建与疑难问题深度分析
前端
IT_陈寒14 小时前
JavaScript的this坑人实录,这次我真的记下来了
前端·人工智能·后端
开开心心就好15 小时前
音频降噪剪辑工具,音量裁剪一键处理
运维·服务器·前端·spring·ocr·音视频·1024程序员节
PedroQue9917 小时前
0.8.2 新增 backFallback 配置,无 tabbar 页返回无忧
前端·uni-app
淡漠的蓝精灵17 小时前
web.py:一个简单到极致的 Python Web 框架
开发语言·前端·python·其他