🧩 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 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_12 小时前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
前端摸鱼匠12 小时前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
lang2015092812 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC13 小时前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务13 小时前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
嘿起屁儿整14 小时前
面试点(网络层面)
前端·网络
VT.馒头14 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
phltxy15 小时前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
Byron070716 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js