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

김동현·2026년 3월 17일

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

소개

<Fragment><>...</> 문법으로 자주 사용되는데요, 감싸는 래퍼 노드 없이 여러 엘리먼트를 그룹으로 묶을 수 있게 해줘요.

Canary: Fragment는 ref도 받을 수 있어요. 이걸 사용하면 래퍼 엘리먼트를 추가하지 않고도 내부 DOM 노드들과 상호작용할 수 있답니다. 아래 레퍼런스와 사용법을 참고하세요.

<>
  <OneChild />
  <AnotherChild />
</>

레퍼런스

<Fragment>

엘리먼트들을 <Fragment>로 감싸서 하나의 엘리먼트가 필요한 상황에서 여러 엘리먼트를 그룹으로 묶을 수 있어요. Fragment로 엘리먼트들을 그룹으로 묶어도 결과 DOM에는 아무런 영향을 주지 않아요. 그냥 엘리먼트들을 그룹으로 묶지 않은 것과 똑같죠. 빈 JSX 태그 <></>는 대부분의 경우에 <Fragment></Fragment>의 축약형이에요.

Props

  • 선택사항 key: 명시적으로 <Fragment> 문법을 사용해서 선언한 Fragment에는 key를 줄 수 있어요.
  • Canary 선택사항 ref: ref 객체(예를 들면 useRef에서 가져온 것)나 콜백 함수를 넣을 수 있어요. React는 Fragment가 감싸고 있는 DOM 노드들과 상호작용하기 위한 메서드들을 구현한 FragmentInstance를 ref 값으로 제공해줘요.

Canary FragmentInstance

Fragment에 ref를 전달하면, React는 Fragment가 감싸고 있는 DOM 노드들과 상호작용하기 위한 메서드들이 있는 FragmentInstance 객체를 제공해줘요:

이벤트 처리 메서드:

  • addEventListener(type, listener, options?): Fragment의 모든 1단계(최상위) DOM 자식들에 이벤트 리스너를 추가해요.
  • removeEventListener(type, listener, options?): Fragment의 모든 1단계(최상위) DOM 자식들에서 이벤트 리스너를 제거해요.
  • dispatchEvent(event): Fragment의 가상 자식에 이벤트를 디스패치해서 추가된 리스너들을 호출하고, DOM 부모로 버블링될 수 있어요.

레이아웃 메서드:

  • compareDocumentPosition(otherNode): Fragment의 문서 위치를 다른 노드와 비교해요.
    • Fragment에 자식이 있으면, 네이티브 compareDocumentPosition 값이 반환돼요.
    • 빈 Fragment는 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가 Fragment 내에 있으면 포커스를 제거해요.

옵저버 메서드:

  • observeUsing(observer): IntersectionObserver나 ResizeObserver로 Fragment의 DOM 자식들을 관찰하기 시작해요.
  • unobserveUsing(observer): 지정된 옵저버로 Fragment의 DOM 자식들 관찰을 중단해요.

주의사항

  • Fragment에 key를 전달하고 싶다면, <>...</> 문법을 사용할 수 없어요. 반드시 'react'에서 Fragment를 명시적으로 import하고 <Fragment key={yourKey}>...</Fragment>로 렌더링해야 해요.

  • React는 <><Child /></>를 렌더링하다가 [<Child />]로 바꾸거나 다시 되돌릴 때, 또는 <><Child /></>를 렌더링하다가 <Child />로 바꾸거나 다시 되돌릴 때 state를 초기화하지 않아요. 이건 한 단계 깊이에서만 동작하는데요: 예를 들어 <><><Child /></></>에서 <Child />로 가면 state가 초기화돼요. 정확한 동작 방식은 여기에서 확인할 수 있어요.

  • Canary Fragment에 ref를 전달하고 싶다면, <>...</> 문법을 사용할 수 없어요. 반드시 'react'에서 Fragment를 명시적으로 import하고 <Fragment ref={yourRef}>...</Fragment>로 렌더링해야 해요.


사용법

여러 엘리먼트 반환하기

Fragment 또는 그와 동일한 <>...</> 문법을 사용해서 여러 엘리먼트를 하나로 묶을 수 있어요. 하나의 엘리먼트만 들어갈 수 있는 곳 어디에서든 여러 엘리먼트를 넣기 위해 사용할 수 있죠. 예를 들어 컴포넌트는 하나의 엘리먼트만 반환할 수 있는데, Fragment를 사용하면 여러 엘리먼트를 하나로 묶어서 그룹으로 반환할 수 있어요:

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

Fragment가 유용한 이유는 Fragment로 엘리먼트들을 그룹으로 묶어도 레이아웃이나 스타일에 아무런 영향을 주지 않기 때문이에요. DOM 엘리먼트 같은 다른 컨테이너로 엘리먼트들을 감쌌을 때와는 다르죠. 브라우저 도구로 아래 예제를 검사해보면, 모든 <h1><article> DOM 노드들이 주위에 래퍼 없이 형제(sibling)로 나타나는 걸 확인할 수 있을 거예요:

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를 작성하는 방법은? {/how-to-write-a-fragment-without-the-special-syntax/}

위의 예제는 React에서 Fragment를 import하는 것과 동일해요:

// {1,5,8}
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 엘리먼트 문법을 사용해야 해요:

// {3,6}
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 DOM 상호작용을 위한 Fragment ref 사용하기

Fragment ref를 사용하면 추가적인 래퍼 엘리먼트를 추가하지 않고도 Fragment가 감싸고 있는 DOM 노드들과 상호작용할 수 있어요. 이벤트 처리, 가시성 추적, 포커스 관리, 그리고 ReactDOM.findDOMNode() 같은 더 이상 사용되지 않는(deprecated) 패턴을 대체하는 데 유용해요.

import { Fragment } from 'react';

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

Canary Fragment ref로 가시성 추적하기

Fragment ref는 가시성 추적과 교차 관찰(intersection observation)에 유용해요. 자식 컴포넌트가 ref를 노출하지 않아도 콘텐츠가 보이는지 모니터링할 수 있게 해준답니다:

// {19,21,31-34}
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>
  );
}

이 패턴은 Effect 기반의 가시성 로깅에 대한 대안이에요. Effect만에 의존하는 것은 대부분의 경우 안티패턴인데, 렌더링된 컴포넌트가 사용자에게 실제로 관찰 가능한지를 보장하지 않기 때문이에요.

부연 설명: 즉, useEffect로 "마운트됐으니까 보이겠지"라고 가정하는 건 정확하지 않아요. 실제로 화면에 보이는지를 확인하려면 IntersectionObserver 같은 브라우저 API를 사용하는 게 더 정확하고, Fragment ref의 observeUsing 메서드가 바로 그걸 편리하게 해주는 거예요.


Canary Fragment ref로 포커스 관리하기

Fragment ref는 Fragment 내의 모든 DOM 노드에 걸쳐 동작하는 포커스 관리 메서드를 제공해요:

import { Fragment, useRef } from 'react';

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

focus() 메서드는 Fragment 내의 첫 번째 포커스 가능한 엘리먼트에 포커스를 주고, focusLast()는 마지막 포커스 가능한 엘리먼트에 포커스를 줘요.


사이트맵

모든 문서 페이지 개요

profile
프론트에_가까운_풀스택_개발자

0개의 댓글