Fragment :从基本原理到深度解析

一、Fragment 的核心概念

Fragment 是 React 提供的一个特殊组件,它用于在不引入额外 DOM 节点的前提下,渲染多个子元素。 它不仅解决了 JSX 要求必须有一个单一父元素的限制,同时避免了因额外容器元素导致的性能损耗和 DOM 结构冗余。

1. 为什么需要 Fragment?

在 React 的 JSX 中规定,每个组件的返回值必须是一个单一的根元素。例如:

jsx 复制代码
function App() {
  return (
    <div>
      <h1>标题</h1>
      <p>内容</p>
    </div> 
  );
}

在这里,你必须使用 <div> 来包裹两个子元素,然而,你会发现,如果这样做,那么DOM 中就会多出一个无意义的 <div>,非常影响样式和性能。

而使用 Fragment 刚好可以完美解决这个问题。


二、Fragment 的核心特性

1. 避免冗余 DOM 结构

Fragment 允许直接返回多个元素,而无需添加额外的容器节点。

示例:使用 Fragment 替代 <div>

jsx 复制代码
function App() {
  return (
    <Fragment>
      <h1>标题</h1>
      <p>内容</p>
    </Fragment>
  );
}

对比:

  • 使用 <div>
  • 使用 Fragment:

分析: 传统的 <div> 会生成额外的 DOM 节点,导致不必要的层级嵌套,Fragment 通过虚拟 DOM 机制,直接将子元素插入目标位置,避免生成真实 DOM 节点。


2. 提升性能

Fragment 的底层就是基于 **文档碎片(DocumentFragment)**的,它可以通过批量操作 DOM 来减少重排重绘的次数。

示例:

html 复制代码
<script>
  const fragment = document.createDocumentFragment();
  const title = document.createElement('h1');
  const content = document.createElement('p');
  fragment.appendChild(title);
  fragment.appendChild(content);
  document.body.appendChild(fragment); // 一次性插入,减少重排
</script>

分析:

  • 如果按照传统方法,那么逐个插入 DOM 元素(如使用appendChild(wrapper)),一定会导致页面的多次重排重绘,这样非常消耗性能。

  • 而通过上面的代码,我们先将所有元素添加到文档碎片中,再一次性插入 DOM,这样减少浏览器的渲染压力,比如:

    • document.createDocumentFragment():这里我们创建了一个虚拟 DOM 容器DocumentFragment,用来临时存储多个 DOM 元素。

    • 而且,这个节点非常特殊,它不会被插入到实际的 DOM 树中,并且可以像普通 DOM 节点一样操作(如添加子元素)。

    • 因此,它也不会触发浏览器的重排(Reflow)和重绘(Repaint) 操作。

    • 最终,在将其他子标签添加到这个容器后,它就将 DocumentFragment 中的所有子元素一次性插入到 <body> 中。


3. 支持 key 属性

在列表渲染中,Fragment 必须配合 key 属性使用,以避免 React 警告。

示例:列表中的Fragment

jsx 复制代码
const items = [
  { id: 1, title: '标题1', content: '内容1' },
  { id: 2, title: '标题2', content: '内容2' }
];

function Demo({ items }) {
  return items.map(item => (
    <Fragment key={item.id}>
      <h1>{item.title}</h1>
      <p>{item.content}</p>
    </Fragment>
  ));
}

分析:

React 要求列表中的每个元素必须有唯一 key,否则会抛出警告,因为 Fragment 添加 key之后,才能确保 React 能正确识别每个子元素组,并且key 只能用在 Fragment 的顶层,不能嵌套使用。


四、Fragment 的实现原理

1. 语法糖 vs 显式导入

  • 语法糖<Fragment></Fragment> 可以简写为<></> ,这也是我们创建一个React项目后App.jsx中常见的标签。

  • 显式导入 :如果需要像上面讲得一样使用Fragment标签,那么你要先从 react 中导入 Fragment 组件才能使用。

jsx 复制代码
import { Fragment } from 'react';

到这里你可能会想,既然能简写成<></>,我为什么还要使用更复杂的Fragment

那么这里就要看使用场景了。

<></> 更简洁,适合简单的场景,而显式导入 Fragment 则可以指定 key 或添加注释,所以我们一般优先使用 <></>,只有在需要显式配置时才用 Fragment


2. 虚拟 DOM 的优化

上面说过,React 的虚拟 DOM 会将 Fragment 的子元素直接插入到真实 DOM 中,不会创建实际的节点。例如:

jsx 复制代码
<>
  <h1>标题</h1>
  <p>内容</p>
</>

实际上,它会被转换为:

javascript 复制代码
React.createElement(React.Fragment, null,
  React.createElement('h1', null, '标题'),
  React.createElement('p', null, '内容')
);

分析: React 会通过虚拟 DOM 对比差异,把 Fragment 的子元素直接插入目标位置,来避免生成额外的 DOM 节点,减少内存占用和渲染时间。


五、总结

1. Fragment 的核心优势

  • 简化 DOM 结构:避免不必要的容器节点。
  • 提升性能:减少 DOM 操作次数,优化重排重绘。
  • 灵活渲染:支持多元素返回和条件渲染。

2. 最佳实践

  • 优先使用 Fragment:在需要返回多个元素时,优先选择 Fragment。
  • 列表渲染中使用 key:确保每个 Fragment 有唯一的标识。
  • 避免嵌套 Fragment:过度嵌套可能导致可读性下降。

相关推荐
@ chen28 分钟前
Spring Boot 解决跨域问题
java·spring boot·后端
一嘴一个橘子1 小时前
react 路由 react-router-dom
react.js
转转技术团队2 小时前
转转上门隐私号系统的演进
java·后端
薛定谔的算法2 小时前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架
【本人】2 小时前
Django基础(二)———URL与映射
后端·python·django
Humbunklung2 小时前
Rust 模块系统:控制作用域与私有性
开发语言·后端·rust
WanderInk3 小时前
依赖对齐不再“失联”:破解 feign/BaseBuilder 错误实战
java·后端·架构
Adolf_19933 小时前
React 中 props 的最常用用法精选+useContext
前端·javascript·react.js
前端小趴菜053 小时前
react - 根据路由生成菜单
前端·javascript·react.js
喝拿铁写前端3 小时前
`reduce` 究竟要不要用?到底什么时候才“值得”用?
前端·javascript·面试