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

<Fragment><>...</> 문법으로 자주 사용되며, 래퍼 노드 없이 엘리먼트를 그룹화할 수 있게 해줍니다.

Canary

Fragment는 ref를 받을 수도 있으며, 래퍼 엘리먼트를 추가하지 않고도 기본 DOM 노드와 상호작용할 수 있습니다. 아래 레퍼런스와 사용법을 참고하세요.
<>
<OneChild />
<AnotherChild />
</>

레퍼런스

<Fragment>

하나의 엘리먼트가 필요한 상황에서 엘리먼트를 <Fragment>로 감싸서 그룹화하세요. Fragment 안에서 그룹화된 엘리먼트는 DOM 결과물에 영향을 주지 않습니다. 즉, 엘리먼트가 그룹화되지 않은 것과 같습니다. 대부분의 경우 빈 JSX 태그인 <></><Fragment></Fragment>의 축약형입니다.

Props

  • key(선택사항): 명시적 <Fragment>로 선언된 Fragment에는 key를 사용할 수 있습니다.

  • Canary only ref(선택사항): ref 객체(예: useRef에서 반환된 것) 또는 콜백 함수입니다. React는 Fragment로 감싼 DOM 노드와 상호작용하기 위한 메서드를 구현한 FragmentInstance를 ref 값으로 제공합니다.

주의 사항

  • Fragment에 key를 사용하려면 <>...</> 구문을 사용할 수 없습니다. 명시적으로 react에서 Fragment를 불러오고Import <Fragment key={yourKey}>...</Fragment>를 렌더링해야 합니다.

  • React는 <><Child /></>에서 [<Child />]로 렌더링하거나 (또는 반대의 경우), 혹은 <><Child /></> 에서 <Child /> 렌더링하거나 (또는 반대의 경우) State를 초기화하지 않습니다. 이는 오직 한 단계 깊이Single Level Deep까지만 적용됩니다. 예를 들어 <><><Child /></></> 에서 <Child />로 렌더링하는 것은 State가 초기화됩니다. 정확한 의미는 여기서 확인할 수 있습니다.

  • Canary only Fragment에 ref를 전달하려면 <>...</> 문법을 사용할 수 없습니다. 명시적으로 'react'에서 Fragment를 불러오고 <Fragment ref={yourRef}>...</Fragment>를 렌더링해야 합니다.


Canary only FragmentInstance

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?)

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

fragmentRef.current.addEventListener('click', handleClick);
Parameters
  • type: A string representing the event type to listen for (e.g. 'click', 'focus').
  • listener: The event handler function.
  • optional options: An options object or boolean for capture, matching the DOM addEventListener API.
Returns

addEventListener does not return anything (undefined).


removeEventListener(type, listener, options?)

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

fragmentRef.current.removeEventListener('click', handleClick);
Parameters
  • type: The event type string.
  • listener: The event handler function to remove.
  • optional options: An options object or boolean, matching the DOM removeEventListener API.
Returns

removeEventListener does not return anything (undefined).


dispatchEvent(event)

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: An Event object to dispatch. If bubbles is true, the event bubbles to the Fragment’s parent DOM node.
Returns

true if the event was not cancelled, false if preventDefault() was called.


focus(options?)

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
  • optional options: A FocusOptions object (e.g. { preventScroll: true }).
Returns

focus does not return anything (undefined).


focusLast(options?)

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

fragmentRef.current.focusLast();
Parameters
Returns

focusLast does not return anything (undefined).


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 does not return anything (undefined).


observeUsing(observer)

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 does not return anything (undefined).


unobserveUsing(observer)

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

fragmentRef.current.unobserveUsing(observer);
Parameters
  • observer: The same IntersectionObserver or ResizeObserver instance previously passed to observeUsing.
Returns

unobserveUsing does not return anything (undefined).


getClientRects()

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

const rects = fragmentRef.current.getClientRects();
Returns

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


getRootNode(options?)

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

const root = fragmentRef.current.getRootNode();
Parameters
Returns

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


compareDocumentPosition(otherNode)

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: The DOM node to compare against.
Returns

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?)

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
  • optional alignToTop: A boolean. If true (the default), scrolls the first child to the top of the scrollable area. If false, scrolls the last child to the bottom. Unlike Element.scrollIntoView(), this method does not accept a ScrollIntoViewOptions object.
Returns

scrollIntoView does not return anything (undefined).

Caveats
  • scrollIntoView does not accept an options object. Passing one throws an error. Use the alignToTop boolean instead.
  • When the Fragment has no children, scrollIntoView scrolls the nearest sibling or parent into view as a fallback.

FragmentInstance Caveats

  • Methods that target children (such as addEventListener, observeUsing, and getClientRects) operate on first-level host (DOM) children of the Fragment. They do not directly target children nested inside another DOM element.
  • focus and focusLast search nested children depth-first for focusable elements, unlike event and observer methods which only target first-level host children.
  • observeUsing does not work on text nodes. React logs a warning in development if the Fragment contains only text children.
  • React does not apply event listeners added via addEventListener to hidden <Activity> trees. When an Activity boundary switches from hidden to visible, listeners are applied automatically.
  • Each first-level DOM child of a Fragment with a ref gets a reactFragments property—a Set<FragmentInstance> containing all Fragment instances that own the element. This enables caching a shared observer across multiple Fragments.

사용법

여러 엘리먼트 반환하기

여러 엘리먼트를 함께 그룹화하기 위해 Fragment<>...</> 문법을 사용하세요. 한 개의 엘리먼트가 존재할 수 있는 곳에 여러 엘리먼트를 넣을 수 있습니다. 예를 들어 컴포넌트는 한 개의 엘리먼트만 반환할 수 있지만 Fragment를 사용하여 여러 엘리먼트를 함께 그룹화하여 반환할 수 있습니다.

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

Fragment로 엘리먼트를 그룹화하면 DOM 엘리먼트와 같은 다른 컨테이너로 엘리먼트를 감싸는 경우와는 달리, 레이아웃이나 스타일에 영향을 주지 않기 때문에 Fragment는 효과적입니다. 브라우저로 아래 예시를 검사하면 모든 <h1>, <article> DOM 노드가 래퍼 없이 형제 노드로 나타나는 것을 볼 수 있습니다.

export default function Blog() {
  return (
    <>
      <Post title="An update" body="It's been a while since I posted..." />
      <Post title="My new blog" 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를 작성하는 방법은 무엇인가요?

위의 예시는 React에서 Fragment를 불러오는Import 것과 동일합니다.

import { Fragment } from 'react';

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

일반적으로 Fragmentkey를 넘겨야 하는 경우가 아니라면 이 기능은 필요하지 않습니다.


변수에 여러 엘리먼트 할당

다른 엘리먼트와 마찬가지로 Fragment를 변수에 할당하고 Props로 전달하는 등의 작업을 할 수 있습니다.

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

텍스트와 함께 엘리먼트 그룹화

Fragment를 사용하여 텍스트를 컴포넌트와 함께 그룹화할 수 있습니다.

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

Fragment 리스트 렌더링

<></> 문법을 사용하는 대신 명시적으로 Fragment를 작성해야 하는 상황이 있습니다. 반복을 통해 여러 엘리먼트를 렌더링할 때 각 요소에 key를 할당해야 합니다. 반복 안에 엘리먼트가 Fragment인 경우 key 속성을 제공하기 위해 일반 JSX 엘리먼트 문법을 사용해야 합니다.

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

DOM을 검사하여 Fragment 자식 주위에 래퍼 엘리먼트가 없는 것을 확인할 수 있습니다.

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 Adding event listeners without a wrapper element

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>
    </>
  );
}

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.

자세히 살펴보기

Which children does a Fragment ref target?

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 is a React component, so the FragmentInstance looks through it to find DOM nodes. The targeted children are A, B, and D. C is not targeted because it is nested inside the DOM element B.

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 Managing focus across a group of elements

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>
  );
}

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 Scrolling a group of elements into view

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 Observing visibility without a wrapper element

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="First section" />
        <Card title="Second section" />
      </VisibleGroup>
      <div className="filler">Scroll up</div>
    </div>
  );
}


Canary only Caching a global IntersectionObserver

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.

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="Green section" className="green" />
      </ObservedGroup>
      <div className="filler" />
      <ObservedGroup onIntersection={onBlue}>
        <Card title="Blue section" className="blue" />
      </ObservedGroup>
      <div className="filler">Scroll up</div>
    </div>
  );
}

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.