<Fragment>는 <>...</> 문법으로 자주 사용되며, 래퍼 노드 없이 엘리먼트를 그룹화할 수 있게 해줍니다.
<>
<OneChild />
<AnotherChild />
</>- 레퍼런스
- 사용법
- 여러 엘리먼트 반환하기
- 변수에 여러 엘리먼트 할당
- 텍스트와 함께 엘리먼트 그룹화
Fragment리스트 렌더링- Canary only Adding event listeners without a wrapper element
- Canary only Managing focus across a group of elements
- Canary only Scrolling a group of elements into view
- Canary only Observing visibility without a wrapper element
- Canary only Caching a global IntersectionObserver
레퍼런스
<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.
addEventListenerandremoveEventListenermanage event listeners across all first-level DOM children.dispatchEventdispatches an event on the Fragment, which can bubble to the DOM parent.focus,focusLast, andblurmanage focus across all nested children depth-first.observeUsingandunobserveUsingattach and detachIntersectionObserverorResizeObserverinstances.getClientRectsreturns bounding rectangles of all first-level DOM children.getRootNodereturns the root node of the Fragment’s parent.compareDocumentPositioncompares the Fragment’s position with another node.scrollIntoViewscrolls the Fragment’s children into view.
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 DOMaddEventListenerAPI.
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 DOMremoveEventListenerAPI.
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: AnEventobject to dispatch. Ifbubblesistrue, 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: AFocusOptionsobject (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
- optional
options: AFocusOptionsobject.
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
observer: AnIntersectionObserverorResizeObserverinstance.
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 sameIntersectionObserverorResizeObserverinstance previously passed toobserveUsing.
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
- optional
options: An object with acomposedboolean property, matching the DOMgetRootNodeAPI.
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. Iftrue(the default), scrolls the first child to the top of the scrollable area. Iffalse, scrolls the last child to the bottom. UnlikeElement.scrollIntoView(), this method does not accept aScrollIntoViewOptionsobject.
Returns
scrollIntoView does not return anything (undefined).
Caveats
scrollIntoViewdoes not accept an options object. Passing one throws an error. Use thealignToTopboolean instead.- When the Fragment has no children,
scrollIntoViewscrolls the nearest sibling or parent into view as a fallback.
FragmentInstance Caveats
- Methods that target children (such as
addEventListener,observeUsing, andgetClientRects) operate on first-level host (DOM) children of the Fragment. They do not directly target children nested inside another DOM element. focusandfocusLastsearch nested children depth-first for focusable elements, unlike event and observer methods which only target first-level host children.observeUsingdoes 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
addEventListenerto hidden<Activity>trees. When anActivityboundary switches from hidden to visible, listeners are applied automatically. - Each first-level DOM child of a Fragment with a
refgets areactFragmentsproperty—aSet<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> ); }
자세히 살펴보기
위의 예시는 React에서 Fragment를 불러오는Import 것과 동일합니다.
import { Fragment } from 'react';
function Post() {
return (
<Fragment>
<PostTitle />
<PostBody />
</Fragment>
);
}일반적으로 Fragment에 key를 넘겨야 하는 경우가 아니라면 이 기능은 필요하지 않습니다.
변수에 여러 엘리먼트 할당
다른 엘리먼트와 마찬가지로 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.
자세히 살펴보기
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.