<Fragment> (<>...</>)<Fragment>는 <>...</> 문법으로 자주 사용되는데요, 감싸는 래퍼 노드 없이 여러 엘리먼트를 그룹으로 묶을 수 있게 해줘요.
Canary: Fragment는 ref도 받을 수 있어요. 이걸 사용하면 래퍼 엘리먼트를 추가하지 않고도 내부 DOM 노드들과 상호작용할 수 있답니다. 아래 레퍼런스와 사용법을 참고하세요.
<>
<OneChild />
<AnotherChild />
</>
<Fragment>엘리먼트들을 <Fragment>로 감싸서 하나의 엘리먼트가 필요한 상황에서 여러 엘리먼트를 그룹으로 묶을 수 있어요. Fragment로 엘리먼트들을 그룹으로 묶어도 결과 DOM에는 아무런 영향을 주지 않아요. 그냥 엘리먼트들을 그룹으로 묶지 않은 것과 똑같죠. 빈 JSX 태그 <></>는 대부분의 경우에 <Fragment></Fragment>의 축약형이에요.
key: 명시적으로 <Fragment> 문법을 사용해서 선언한 Fragment에는 key를 줄 수 있어요.ref: ref 객체(예를 들면 useRef에서 가져온 것)나 콜백 함수를 넣을 수 있어요. React는 Fragment가 감싸고 있는 DOM 노드들과 상호작용하기 위한 메서드들을 구현한 FragmentInstance를 ref 값으로 제공해줘요.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의 문서 위치를 다른 노드와 비교해요.compareDocumentPosition 값이 반환돼요.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC을 포함해요.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> ); }보통 이렇게 쓸 일은 없는데,
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를 명시적으로 작성해야 하는 상황이 있어요. 반복문에서 여러 엘리먼트를 렌더링할 때는 각 엘리먼트에 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>
);
}
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>
);
}
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메서드가 바로 그걸 편리하게 해주는 거예요.
Fragment ref는 Fragment 내의 모든 DOM 노드에 걸쳐 동작하는 포커스 관리 메서드를 제공해요:
import { Fragment, useRef } from 'react';
function FocusFragment({ children }) {
return (
<Fragment ref={(fragmentInstance) => fragmentInstance?.focus()}>
{children}
</Fragment>
);
}
focus() 메서드는 Fragment 내의 첫 번째 포커스 가능한 엘리먼트에 포커스를 주고, focusLast()는 마지막 포커스 가능한 엘리먼트에 포커스를 줘요.