<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 节点交互的方法。

注意事项

🌐 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>

Canary only FragmentInstance

当你将 ref 传递给一个 Fragment 时,React 会提供一个 FragmentInstance 对象。它实现了与 Fragment 封装的一级 DOM 子元素交互的方法。

🌐 When you pass a ref to a Fragment, React provides a FragmentInstance object. It implements methods for interacting with the first-level DOM children wrapped by the Fragment.


addEventListener(type, listener, options?)

向 Fragment 的所有第一级 DOM 子项添加事件监听器。

🌐 Adds an event listener to all first-level DOM children of the Fragment.

fragmentRef.current.addEventListener('click', handleClick);
参数

🌐 Parameters

  • type:表示要监听的事件类型的字符串(例如 'click''focus')。
  • listener:事件处理函数。
  • 可选 options:一个用于捕获的选项对象或布尔值,匹配 DOM addEventListener API.
返回

🌐 Returns

addEventListener 不返回任何内容(undefined)。


removeEventListener(type, listener, options?)

从 Fragment 的所有一级 DOM 子节点中移除事件监听器。

🌐 Removes an event listener from all first-level DOM children of the Fragment.

fragmentRef.current.removeEventListener('click', handleClick);
参数

🌐 Parameters

  • type:事件类型字符串。
  • listener:要移除的事件处理函数。
  • 可选 options:一个选项对象或布尔值,符合 DOM removeEventListener API
返回

🌐 Returns

removeEventListener 不返回任何内容(undefined)。


dispatchEvent(event)

在 Fragment 上分发一个事件。已添加的事件监听器会被调用,并且事件可以向 Fragment 的 DOM 父元素冒泡。

🌐 Dispatches an event on the Fragment. Added event listeners are called, and the event can bubble to the Fragment’s DOM parent.

fragmentRef.current.dispatchEvent(new Event('custom', { bubbles: true }));
参数

🌐 Parameters

  • event:要分发的Event对象。如果bubblestrue,事件将冒泡到Fragment的父DOM节点。
返回

🌐 Returns

如果事件没有被取消,则为 true,如果调用了 preventDefault(),则为 false


focus(options?)

将焦点设置到 Fragment 中第一个可聚焦的 DOM 节点。与在 DOM 元素上调用 element.focus() 不同,此方法会深度优先搜索 所有 嵌套子节点,直到找到可聚焦的元素——而不仅仅是元素本身或其直接子节点。

🌐 Focuses the first focusable DOM node in the Fragment. Unlike calling element.focus() on a DOM element, this method searches all nested children depth-first until it finds a focusable element—not just the element itself or its direct children.

fragmentRef.current.focus();
参数

🌐 Parameters

  • 可选 options:一个FocusOptions对象(例如 { preventScroll: true })。
返回

🌐 Returns

focus 不返回任何内容(undefined)。


focusLast(options?)

将焦点设置到片段中最后一个可聚焦的 DOM 节点。按深度优先搜索嵌套子节点,然后反向迭代。

🌐 Focuses the last focusable DOM node in the Fragment. Searches nested children depth-first, then iterates in reverse.

fragmentRef.current.focusLast();
参数

🌐 Parameters

返回

🌐 Returns

focusLast 不返回任何内容(undefined)。


blur()

如果活动元素位于 Fragment 内,则移除其焦点。如果 document.activeElement 不在 Fragment 内,blur 不执行任何操作。

🌐 Removes focus from the active element if it is within the Fragment. If document.activeElement is not within the Fragment, blur does nothing.

fragmentRef.current.blur();
返回

🌐 Returns

blur 不返回任何内容(undefined)。


observeUsing(observer)

使用提供的监视器开始观察 Fragment 的所有一级 DOM 子元素。

🌐 Starts observing all first-level DOM children of the Fragment with the provided observer.

const observer = new IntersectionObserver(callback, options);
fragmentRef.current.observeUsing(observer);
参数

🌐 Parameters

返回

🌐 Returns

observeUsing 不返回任何内容(undefined)。


unobserveUsing(observer)

停止使用指定的观察者观察 Fragment 的 DOM 子项。

🌐 Stops observing the Fragment’s DOM children with the specified observer.

fragmentRef.current.unobserveUsing(observer);
参数

🌐 Parameters

  • observer:之前传递给 observeUsing 的相同 IntersectionObserverResizeObserver 实例。
返回

🌐 Returns

unobserveUsing 不返回任何内容(undefined)。


getClientRects()

返回一个扁平数组,其中包含表示所有一级 DOM 子元素边界矩形的 DOMRect 对象。

🌐 Returns a flat array of DOMRect objects representing the bounding rectangles of all first-level DOM children.

const rects = fragmentRef.current.getClientRects();
返回

🌐 Returns

一个包含所有子元素的边界矩形的 Array<DOMRect>

🌐 An Array<DOMRect> containing the bounding rectangles of all children.


getRootNode(options?)

返回包含 Fragment 的父级 DOM 节点的根节点,行为与 Node.getRootNode() 相匹配。

🌐 Returns the root node containing the Fragment’s parent DOM node, matching the behavior of Node.getRootNode().

const root = fragmentRef.current.getRootNode();
参数

🌐 Parameters

  • 可选 options:一个具有 composed 布尔属性的对象,与 DOM getRootNode API 相匹配。
返回

🌐 Returns

如果没有父 DOM 节点,则为 DocumentShadowRootFragmentInstance 本身。

🌐 A Document, ShadowRoot, or the FragmentInstance itself if there is no parent DOM node.


compareDocumentPosition(otherNode)

将片段的文档位置与另一个节点进行比较,返回一个与 Node.compareDocumentPosition() 行为相匹配的位掩码。

🌐 Compares the document position of the Fragment with another node, returning a bitmask matching the behavior of Node.compareDocumentPosition().

const position = fragmentRef.current.compareDocumentPosition(otherElement);
参数

🌐 Parameters

  • otherNode:要比较的 DOM 节点。
返回

🌐 Returns

一个位置标志的位掩码。空片段和通过门户渲染子元素的片段在结果中包含Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC

🌐 A bitmask of position flags. Empty Fragments and Fragments with children rendered through a portal include Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC in the result.


scrollIntoView(alignToTop?)

将 Fragment 的子元素滚动到可视区域。当 alignToToptrue 或未指定时,滚动以使第一个子元素与可滚动祖级的顶部对齐。当 alignToTopfalse 时,滚动以使最后一个子元素与底部对齐。

🌐 Scrolls the Fragment’s children into view. When alignToTop is true or omitted, scrolls to align the first child with the top of the scrollable ancestor. When alignToTop is false, scrolls to align the last child with the bottom.

fragmentRef.current.scrollIntoView();
参数

🌐 Parameters

  • 可选 alignToTop:一个布尔值。如果为 true(默认值),滚动第一个子元素到可滚动区域的顶部。如果为 false,滚动最后一个子元素到底部。与 Element.scrollIntoView() 不同,该方法不接受 ScrollIntoViewOptions 对象。
返回

🌐 Returns

scrollIntoView 不返回任何内容(undefined)。

注意事项

🌐 Caveats

  • scrollIntoView 不接受选项对象。传递选项对象会导致错误。请改用布尔值 alignToTop
  • 当 Fragment 没有子元素时,scrollIntoView 会将最近的兄弟元素或父元素滚动到视图中作为后备方案。

FragmentInstance 注意事项

🌐 FragmentInstance Caveats

  • 针对子元素的方法(例如 addEventListenerobserveUsinggetClientRects)作用于 Fragment 的第一层主机(DOM)子元素。它们不会直接作用于嵌套在另一个 DOM 元素中的子元素。
  • focusfocusLast 对嵌套的子元素进行深度优先搜索以查找可获取焦点的元素,这与仅针对第一层宿主子元素的事件和观察者方法不同。
  • observeUsing 不适用于文本节点。如果 Fragment 仅包含文本子节点,React 会在开发环境中记录警告。
  • React 不会将通过 addEventListener 添加的事件监听器应用到隐藏的 <Activity> 树上。当 Activity 边界从隐藏切换为可见时,监听器会自动应用。
  • 带有 ref 的 Fragment 的每个一级 DOM 子元素都会获得一个 reactFragments 属性——一个 Set<FragmentInstance>,其中包含拥有该元素的所有 Fragment 实例。这使得可以在多个 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 在没有封装元素的情况下添加事件监听器

片段 ref 允许你向一组元素添加事件监听器,而无需添加封装的 DOM 节点。使用 ref 回调 来附加和清理监听器:

🌐 Fragment refs let you add event listeners to a group of elements without adding a wrapper DOM node. Use a ref callback to attach and clean up listeners:

import { Fragment, useState, useRef, useEffect } from 'react';

function ClickableFragment({ children, onClick }) {
  const fragmentRef = useRef(null);
  useEffect(() => {
    const fragmentInstance = fragmentRef.current;
    if (fragmentInstance === null) {
      return;
    }
    fragmentInstance.addEventListener('click', onClick);
    return () => {
      fragmentInstance.removeEventListener(
        'click',
        onClick
      );
    };
  }, [onClick])
  return (
    <Fragment ref={fragmentRef}>
      {children}
    </Fragment>
  );
}

export default function App() {
  const [clicks, setClicks] = useState(0);

  return (
    <>
      <p>Total clicks: {clicks}</p>
      <ClickableFragment onClick={() => {
        setClicks(c => c + 1);
      }}>
        <button>Button A</button>
        <button>Button B</button>
        <button>Button C</button>
      </ClickableFragment>
    </>
  );
}

addEventListener 调用将监听器应用于 Fragment 的每个一级 DOM 子元素。当子元素被动态添加或移除时,FragmentInstance 会自动添加或移除监听器。

🌐 The addEventListener call applies the listener to every first-level DOM child of the Fragment. When children are dynamically added or removed, the FragmentInstance automatically adds or removes the listener.

深入研究

Fragment 引用针对哪些子元素?

🌐 Which children does a Fragment ref target?

FragmentInstance 目标是 Fragment 的 一级主机(DOM)子元素。考虑这个树结构:

🌐 A FragmentInstance targets the first-level host (DOM) children of the Fragment. Consider this tree:

<Fragment ref={ref}>
<div id="A" />
<Wrapper>
<div id="B">
<div id="C" />
</div>
</Wrapper>
<div id="D" />
</Fragment>

Wrapper 是一个 React 组件,所以 FragmentInstance 会查找它以找到 DOM 节点。目标子元素是 ABDC 没有被针对,因为它嵌套在 DOM 元素 B 内。

addEventListenerobserveUsinggetClientRects 这样的方法作用于这些一级 DOM 子元素。focusfocusLast 则不同——它们深度优先搜索 所有 嵌套子元素以找到可聚焦的元素。

🌐 Methods like addEventListener, observeUsing, and getClientRects operate on these first-level DOM children. focus and focusLast are different—they search all nested children depth-first to find focusable elements.


Canary only 在一组元素中管理焦点

片段 ref 提供 focusfocusLastblur 方法,这些方法可跨片段内的所有 DOM 节点操作:

🌐 Fragment refs provide focus, focusLast, and blur methods that operate across all DOM nodes within the Fragment:

import { Fragment, useRef } from 'react';

function FormFields({ children }) {
  const fragmentRef = useRef(null);

  return (
    <>
      <div className="buttons">
        <button onClick={() => {
          fragmentRef.current.focus();
        }}>
          Focus first
        </button>
        <button onClick={() => {
          fragmentRef.current.focusLast();
        }}>
          Focus last
        </button>
        <button onClick={() => {
          fragmentRef.current.blur();
        }}>
          Blur
        </button>
      </div>
      <Fragment ref={fragmentRef}>
        {children}
      </Fragment>
    </>
  );
}

// Even though the inputs are deeply nested,
// focus() searches depth-first to find them.
export default function App() {
  return (
    <FormFields>
      <fieldset>
        <legend>Shipping</legend>
        <label>
          Street: <input name="street" />
        </label>
        <label>
          City: <input name="city" />
        </label>
      </fieldset>
    </FormFields>
  );
}

调用 focus() 会将焦点放在 street 输入框上——即使它嵌套在 <fieldset><label> 内。focus() 会对所有嵌套子元素进行深度优先搜索,而不仅仅是 Fragment 的直接子元素。focusLast() 以相反的顺序执行相同操作,而 blur() 会在当前聚焦元素位于 Fragment 内时移除焦点。

🌐 Calling focus() focuses the street input—even though it is nested inside a <fieldset> and <label>. focus() searches depth-first through all nested children, not just direct children of the Fragment. focusLast() does the same in reverse, and blur() removes focus if the currently focused element is within the Fragment.


Canary only 将一组元素滚动到视图中

使用 scrollIntoView 将 Fragment 的子元素滚动到视图中,而无需封装元素。传递 true(或省略该参数)可将第一个子元素滚动到顶部。传递 false 可将最后一个子元素滚动到底部:

🌐 Use scrollIntoView to scroll a Fragment’s children into view without a wrapper element. Pass true (or omit the argument) to scroll the first child to the top. Pass false to scroll the last child to the bottom:

import { Fragment, useRef } from 'react';

function ScrollableSection({ children }) {
  const fragmentRef = useRef(null);

  return (
    <>
      <div className="buttons">
        <button onClick={() => {
          fragmentRef.current.scrollIntoView();
        }}>
          Scroll to top
        </button>
        <button onClick={() => {
          fragmentRef.current.scrollIntoView(false);
        }}>
          Scroll to bottom
        </button>
      </div>
      <div className="container">
        <Fragment ref={fragmentRef}>
          {children}
        </Fragment>
      </div>
    </>
  );
}

const items = [];
for (let i = 1; i <= 25; i++) {
  items.push('Item ' + i);
}

export default function App() {
  return (
    <ScrollableSection>
      <h3>Section Start</h3>
      {items.map((item) => (
        <p key={item}>{item}</p>
      ))}
      <h3>Section End</h3>
    </ScrollableSection>
  );
}


Canary only 在没有封装元素的情况下观察可见性

使用 observeUsingIntersectionObserver 附加到 Fragment 的所有一级 DOM 子元素上。这使你可以跟踪可见性,而无需子组件公开 ref 或添加封装元素:

🌐 Use observeUsing to attach an IntersectionObserver to all first-level DOM children of a Fragment. This lets you track visibility without requiring child components to expose refs or adding a wrapper element:

import {
  Fragment,
  useRef,
  useLayoutEffect,
  useState,
} from 'react';
import Card from './Card';

function VisibleGroup({ onVisibilityChange, children }) {
  const fragmentRef = useRef(null);

  useLayoutEffect(() => {
    const visibleElements = new Set();
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(e => {
          if (e.isIntersecting) {
            visibleElements.add(e.target);
          } else {
            visibleElements.delete(e.target);
          }
        });
        onVisibilityChange(visibleElements.size > 0);
      }
    );
    const fragmentInstance = fragmentRef.current;
    fragmentInstance.observeUsing(observer);
    return () => {
      fragmentInstance.unobserveUsing(observer);
    };
  }, [onVisibilityChange]);

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

export default function App() {
  const [isVisible, setIsVisible] = useState(true);

  return (
    <div className={isVisible ? 'page visible' : 'page'}>
      <div className="filler">Scroll down</div>
      <VisibleGroup onVisibilityChange={setIsVisible}>
        <Card title="第一部分" />
        <Card title="第二部分" />
      </VisibleGroup>
      <div className="filler">Scroll up</div>
    </div>
  );
}


Canary only 缓存全局 IntersectionObserver

对于拥有许多观察者的网站,一个常见的性能优化是为每个配置共享一个IntersectionObserver,并根据哪个元素交叉将其条目路由到正确的回调。片段ref通过reactFragments属性支持相同的模式。

🌐 A common performance optimization for sites with many observers is to share a single IntersectionObserver per config and route its entries to the correct callbacks based on which element intersected. Fragment refs support this same pattern through the reactFragments property.

带有 ref 的 Fragment 的每个一级 DOM 子元素都有一个 reactFragments 属性:一个包含该元素的 FragmentInstance 对象的 Set。当共享监视器触发时,你可以使用此属性查找哪个 FragmentInstance 拥有相交的元素并运行正确的回调。

🌐 Each first-level DOM child of a Fragment with a ref has a reactFragments property: a Set of FragmentInstance objects that contain that element. When the shared observer fires, you can use this property to look up which FragmentInstance owns the intersecting element and run the right callbacks.

import { useState, useCallback } from 'react';
import ObservedGroup from './ObservedGroup';
import Card from './Card';

export default function App() {
  const [bgColor, setBgColor] = useState(null);

  const onGreen = useCallback((entry) => {
    if (entry.isIntersecting) {
      setBgColor('#d4edda');
    }
  }, []);

  const onBlue = useCallback((entry) => {
    if (entry.isIntersecting) {
      setBgColor('#cce5ff');
    }
  }, []);

  return (
    <div className="page" style={{
      background: bgColor || 'white',
    }}>
      <div className="filler">Scroll down</div>
      <ObservedGroup onIntersection={onGreen}>
        <Card title="绿色部分" className="green" />
      </ObservedGroup>
      <div className="filler" />
      <ObservedGroup onIntersection={onBlue}>
        <Card title="蓝色部分" className="blue" />
      </ObservedGroup>
      <div className="filler">Scroll up</div>
    </div>
  );
}

具有相同选项的多个 ObservedGroup 组件重用单个 IntersectionObserver。当任一部分滚动到视图中时,共享的观察者会触发,并使用 reactFragments 将条目路由到正确的回调。

🌐 Multiple ObservedGroup components with the same options reuse a single IntersectionObserver. When either section scrolls into view, the shared observer fires and uses reactFragments to route the entry to the correct callback.