<Fragment> (<>...</>)

<Fragment>,通常通过 <>...</> 语法使用,让你在不使用封装节点的情况下对元素进行分组。

Canary

碎片也可以接受 refs,这使得在不添加封装元素的情况下与底层 DOM 节点进行交互成为可能。请参见下面的参考和用法。
<>
<OneChild />
<AnotherChild />
</>

参考

🌐 Reference

<Fragment>

Wrap elements in <Fragment> to group them together in situations where you need a single element. Grouping elements in Fragment has no effect on the resulting DOM; it is the same as if the elements were not grouped. The empty JSX tag <></> is shorthand for <Fragment></Fragment> in most cases.

属性

🌐 Props

  • 可选 key:使用显式 <Fragment> 语法声明的片段可能具有 键。
  • Canary only 可选 ref:一个 ref 对象(例如来自 useRef)或 回调函数。React 提供了一个 FragmentInstance 作为 ref 值,它实现了与 Fragment 封装的 DOM 节点交互的方法。

Canary only 片段实例

当你将 ref 传递给一个片段时,React 会提供一个带有用于与片段封装的 DOM 节点交互的方法的 FragmentInstance 对象:

🌐 When you pass a ref to a fragment, React provides a FragmentInstance object with methods for interacting with the DOM nodes wrapped by the fragment:

事件处理方法:

  • addEventListener(type, listener, options?):向 Fragment 的所有第一级 DOM 子项添加事件监听器。
  • removeEventListener(type, listener, options?):从 Fragment 的所有一级 DOM 子节点中移除事件监听器。
  • dispatchEvent(event):向 Fragment 的虚拟子级分派事件,以调用任何添加的监听器,并可以冒泡到 DOM 父级。

布局方法:

  • compareDocumentPosition(otherNode):将 Fragment 的文档位置与其他节点进行比较。
    • 如果 Fragment 有子元素,则返回原生 compareDocumentPosition 值。
    • 空片段将尝试比较在 React 树中的位置并包含 Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
    • 由于门户或其他插入而在 React 树和 DOM 树中具有不同关系的元素是 Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
  • getClientRects():返回一个扁平数组,包含表示所有子对象边界矩形的 DOMRect 对象。
  • getRootNode():返回包含 Fragment 父 DOM 节点的根节点。

焦点管理方法:

  • focus(options?):将焦点设置到 Fragment 中第一个可聚焦的 DOM 节点。焦点会尝试按深度优先方式设置到嵌套的子元素上。
  • focusLast(options?):将焦点设置到 Fragment 中最后一个可聚焦的 DOM 节点。焦点会尝试按深度优先顺序设置到嵌套子元素上。
  • blur():如果 document.activeElement 位于片段中,则移除焦点。

观察者方法:

  • observeUsing(observer):开始使用 IntersectionObserver 或 ResizeObserver 观察 Fragment 的 DOM 子项。
  • unobserveUsing(observer):停止使用指定的观察者观察 Fragment 的 DOM 子项。

注意事项

🌐 Caveats

  • 如果你想将 key 传递给一个 Fragment,你不能使用 <>...</> 语法。你必须明确从 'react' 导入 Fragment 并渲染 <Fragment key={yourKey}>...</Fragment>
  • 当你从渲染 <><Child /></> 变为 [<Child />] 或返回,或者从渲染 <><Child /></> 变为 <Child /> 并返回时,React 不会重置状态。这只在单层有效:例如,从 <><><Child /></></><Child /> 会重置状态。准确的语义请参见这里
  • Canary only 如果你想将 ref 传递给一个 Fragment,你不能使用 <>...</> 语法。你必须从 'react' 明确导入 Fragment 并渲染 <Fragment ref={yourRef}>...</Fragment>

用法

🌐 Usage

返回多个元素

🌐 Returning multiple elements

使用 Fragment,或等效的 <>...</> 语法,将多个元素组合在一起。你可以在任何单个元素可以放置的地方使用它来放置多个元素。例如,一个组件只能返回一个元素,但通过使用 Fragment,你可以将多个元素组合在一起,然后作为一个组返回它们:

🌐 Use Fragment, or the equivalent <>...</> syntax, to group multiple elements together. You can use it to put multiple elements in any place where a single element can go. For example, a component can only return one element, but by using a Fragment you can group multiple elements together and then return them as a group:

function Post() {
return (
<>
<PostTitle />
<PostBody />
</>
);
}

Fragments 很有用,因为使用 Fragment 对元素进行分组不会影响布局或样式,这与将元素封装在另一个容器(如 DOM 元素)中不同。如果你使用浏览器工具查看这个示例,你会看到所有 <h1><article> DOM 节点作为兄弟节点出现,没有封装它们的容器:

🌐 Fragments are useful because grouping elements with a Fragment has no effect on layout or styles, unlike if you wrapped the elements in another container like a DOM element. If you inspect this example with the browser tools, you’ll see that all <h1> and <article> DOM nodes appear as siblings without wrappers around them:

export default function Blog() {
  return (
    <>
      <Post title="更新" body="It's been a while since I posted..." />
      <Post title="我的新博客" body="I am starting a new blog!" />
    </>
  )
}

function Post({ title, body }) {
  return (
    <>
      <PostTitle title={title} />
      <PostBody body={body} />
    </>
  );
}

function PostTitle({ title }) {
  return <h1>{title}</h1>
}

function PostBody({ body }) {
  return (
    <article>
      <p>{body}</p>
    </article>
  );
}

深入研究

如何在没有特殊语法的情况下编写 Fragment?

🌐 How to write a Fragment without the special syntax?

上面的例子等同于从 React 导入 Fragment

🌐 The example above is equivalent to importing Fragment from React:

import { Fragment } from 'react';

function Post() {
return (
<Fragment>
<PostTitle />
<PostBody />
</Fragment>
);
}

通常你不需要这个,除非你需要key 传递给你的 Fragment.

🌐 Usually you won’t need this unless you need to pass a key to your Fragment.


将多个元素分配给变量

🌐 Assigning multiple elements to a variable

与任何其他元素一样,你可以将 Fragment 元素分配给变量,将它们作为属性传递,以此类推:

🌐 Like any other element, you can assign Fragment elements to variables, pass them as props, and so on:

function CloseDialog() {
const buttons = (
<>
<OKButton />
<CancelButton />
</>
);
return (
<AlertDialog buttons={buttons}>
Are you sure you want to leave this page?
</AlertDialog>
);
}

对使用文本的元素进行分组

🌐 Grouping elements with text

你可以使用 Fragment 将文本与组件组合在一起:

🌐 You can use Fragment to group text together with components:

function DateRangePicker({ start, end }) {
return (
<>
From
<DatePicker date={start} />
to
<DatePicker date={end} />
</>
);
}

渲染 Fragment 列表

🌐 Rendering a list of Fragments

这是一个需要显式编写 Fragment 而不是使用 <></> 语法的情况。当你在循环中渲染多个元素时,你需要为每个元素分配一个 key。如果循环中的元素是 Fragments,你需要使用普通的 JSX 元素语法来提供 key 属性:

🌐 Here’s a situation where you need to write Fragment explicitly instead of using the <></> syntax. When you render multiple elements in a loop, you need to assign a key to each element. If the elements within the loop are Fragments, you need to use the normal JSX element syntax in order to provide the key attribute:

function Blog() {
return posts.map(post =>
<Fragment key={post.id}>
<PostTitle title={post.title} />
<PostBody body={post.body} />
</Fragment>
);
}

你可以检查 DOM 以验证 Fragment 子元素周围没有封装元素:

🌐 You can inspect the DOM to verify that there are no wrapper elements around the Fragment children:

import { Fragment } from 'react';

const posts = [
  { id: 1, title: 'An update', body: "It's been a while since I posted..." },
  { id: 2, title: 'My new blog', body: 'I am starting a new blog!' }
];

export default function Blog() {
  return posts.map(post =>
    <Fragment key={post.id}>
      <PostTitle title={post.title} />
      <PostBody body={post.body} />
    </Fragment>
  );
}

function PostTitle({ title }) {
  return <h1>{title}</h1>
}

function PostBody({ body }) {
  return (
    <article>
      <p>{body}</p>
    </article>
  );
}


Canary only 使用 Fragment 引用进行 DOM 交互

Fragment 引用允许你与由 Fragment 封装的 DOM 节点进行交互,而无需添加额外的封装元素。这对于事件处理、可见性跟踪、焦点管理以及替换像 ReactDOM.findDOMNode() 这样的弃用模式非常有用。

🌐 Fragment refs allow you to interact with the DOM nodes wrapped by a Fragment without adding extra wrapper elements. This is useful for event handling, visibility tracking, focus management, and replacing deprecated patterns like ReactDOM.findDOMNode().

import { Fragment } from 'react';

function ClickableFragment({ children, onClick }) {
return (
<Fragment ref={fragmentInstance => {
fragmentInstance.addEventListener('click', handleClick);
return () => fragmentInstance.removeEventListener('click', handleClick);
}}>
{children}
</Fragment>
);
}

Canary only 使用 Fragment 引用跟踪可见性

Fragment refs 对可见性跟踪和交叉观察很有用。这使你能够在内容变为可见时进行监控,而无需子组件公开 refs:

🌐 Fragment refs are useful for visibility tracking and intersection observation. This enables you to monitor when content becomes visible without requiring the child Components to expose refs:

import { Fragment, useRef, useLayoutEffect } from 'react';

function VisibilityObserverFragment({ threshold = 0.5, onVisibilityChange, children }) {
const fragmentRef = useRef(null);

useLayoutEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
onVisibilityChange(entries.some(entry => entry.isIntersecting))
},
{ threshold }
);

fragmentRef.current.observeUsing(observer);
return () => fragmentRef.current.unobserveUsing(observer);
}, [threshold, onVisibilityChange]);

return (
<Fragment ref={fragmentRef}>
{children}
</Fragment>
);
}

function MyComponent() {
const handleVisibilityChange = (isVisible) => {
console.log('Component is', isVisible ? 'visible' : 'hidden');
};

return (
<VisibilityObserverFragment onVisibilityChange={handleVisibilityChange}>
<SomeThirdPartyComponent />
<AnotherComponent />
</VisibilityObserverFragment>
);
}

这种模式是基于效果的可见性日志的替代方案,而在大多数情况下,基于效果的可见性日志是一种反模式。仅依赖效果并不能保证渲染的组件对用户是可观察的。

🌐 This pattern is an alternative to Effect-based visibility logging, which is an anti-pattern in most cases. Relying on Effects alone does not guarantee that the rendered Component is observable by the user.


Canary only 使用 Fragment 引用进行焦点管理

Fragment 引用提供适用于 Fragment 内所有 DOM 节点的焦点管理方法:

🌐 Fragment refs provide focus management methods that work across all DOM nodes within the Fragment:

import { Fragment, useRef } from 'react';

function FocusFragment({ children }) {
return (
<Fragment ref={(fragmentInstance) => fragmentInstance?.focus()}>
{children}
</Fragment>
);
}

focus() 方法将焦点设置在 Fragment 中的第一个可聚焦元素上,而 focusLast() 将焦点设置在最后一个可聚焦元素上。

🌐 The focus() method focuses the first focusable element within the Fragment, while focusLast() focuses the last focusable element.