참조<Suspense>Props주의사항사용법1. 콘텐츠를 로딩하는 동안 fallback 표시하기📝Note2. 콘텐츠를 한 번에 드러내기3. 중첩된 콘텐츠가 로드될 때 표시하기3. 새 콘텐츠가 로드되는 동안 오래된 콘텐츠 표시하기📝Note4. 이미 표시된 콘텐츠가 숨겨지지 않도록 방지하기📝Note5. 트랜지션이 발생하고 있음을 나타내기6. 탐색시 Suspense 경계 재설정하기7. 서버 오류 및 서버 전용 콘텐츠에 대한 fallback 제공하기문제 해결업데이트 중에 UI가 폴백으로 대체되는 것을 방지하려면 어떻게 해야 할까요?
<Suspense>
를 사용하면 children 컴포넌트가 로딩을 완료할 때까지 fallback을 표시할 수 있습니다.※ fallback 이란 ? 대체 시스템
<Suspense fallback={<Loading />}> <SomeComponent /> </Suspense>
참조
<Suspense>
Props
children
: 렌더링하려는 실제 UI입니다. 렌더링하는 동안children
이 일시 중단되면 Suspense 경계가fallback
렌더링으로 전환됩니다.
JSX 태그 내에 콘텐츠를 중첩하면, 부모 컴포넌트는 해당 컨텐츠를
children
이라는 prop으로 받는다.fallback
: 로딩이 완료되지 않은 경우에 실제 UI 대신 렌더링할 대체 UI입니다.- 유효한 어떤 React 노드
- 로딩 스피너나 스켈레톤과 같은 가벼운 플레이스홀더 뷰
children
이 일시 중단 → 자동으로 fallback
으로 전환 → 다시 children
으로 전환(데이터가 준비되었다) 렌더링 중에
fallback
이 일시 중단되면 가장 가까운 상위 Suspense 경계가 활성화됩니다.주의사항
- React는 처음 마운트하기 전에 일시 중단된 렌더링의 state를 보존하지 않습니다. 컴포넌트가 로드되면 React는 일시 중단된 트리의 렌더링을 처음부터 다시 시도합니다.
- Suspense가 트리에 대한 콘텐츠를 표시하고 있다가 다시 일시 중단된 경우, 그 업데이트의 원인이
startTransition
이나useDeferredValue
로 인한 것이 아니라면fallback
이 다시 표시됩니다.
- React가 다시 일시 중단되어 이미 표시된 콘텐츠를 숨겨야 하는 경우, 콘텐츠 트리에서 layout Effect를 클린업 합니다. 콘텐츠가 다시 표시될 준비가 되면 React는 layout Effect를 다시 실행합니다. 이를 통해 콘텐츠가 숨겨져 있는 동안에는 DOM 레이아웃을 측정하는 Effect가 해당 작업을 시도하지 않도록 합니다.
layouteffect → 브라우저가 화면을 다시 채우기 전에 실행되는 버전의
useEffect
입니다.- React에는 Suspense와 통합된 스트리밍 서버 렌더링 (streaming server rendering)및 선택적 Hydration과 같은 내부 최적화가 포함되어 있습니다. 아키텍처 개요 및 기술 강연에서 자세히 알아보세요.
사용법
1. 콘텐츠를 로딩하는 동안 fallback 표시하기
애플리케이션의 어떤 부분이든 Suspense 경계로 감쌀 수 있습니다:
<Suspense fallback={<Loading />}> <Albums /> </Suspense>
React는 자식에게 필요한 모든 코드와 데이터가 로드될 때까지 로딩 폴백을 표시합니다.
- 앨범 목록을 가져오는 동안
Albums
컴포넌트가 일시 중단됩니다.
- 렌더링할 준비가 될 때까지 React는 가장 가까운 상위 Suspense boundary를 fallback인
Loading
컴포넌트로 전환하여 표시합니다.
- 이후 데이터가 로드되면 React는
Loading
fallback을 숨기고 데이터와 함께Albums
컴포넌트를 렌더링합니다.
import { Suspense } from 'react'; import Albums from './Albums.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </> ); } function Loading() { return <h2>🌀 Loading...</h2>;
📝Note
오직 Suspense를 도입한 데이터 소스에서만 Suspense 컴포넌트를 활성화할 수 있습니다.
- Suspense 도입 프레임워크를 사용한 데이터 fetching
lazy
를 사용한 지연 로딩 컴포넌트 코드
⚠️ Suspense는 Effect나 이벤트 핸들러 내부에서 fetching하는 경우를 감지하지 않습니다.
위의
Albums
컴포넌트에서 데이터를 로드하는 정확한 방법은 프레임워크에 따라 다릅니다. Suspense를 도입한 프레임워크를 사용하는 경우, 해당 프레임워크의 데이터 fetching 문서에서 자세한 내용을 확인할 수 있을 것입니다. (예를들어 react query)
잘 알려진 프레임워크를 사용하지 않고 데이터 페칭에 Suspense를 도입하는 방법은 아직 지원되지 않습니다
2. 콘텐츠를 한 번에 드러내기
기본적으로 Suspense 내부의 전체 트리는 단일 단위로 취급됩니다. 예를 들어, 다음 컴포넌트 중 하나만 데이터 대기를 위해 일시 중단하더라도 모든 컴포넌트가 함께 로딩 표시기로 대체됩니다:
이후 모든 항목이 표시될 준비가 되면, 이들 모두가 한꺼번에 표시됩니다.
아래 예제에서는
Biography
와 Albums
모두 데이터를 가져옵니다. 그러나 이 두 컴포넌트는 단일 Suspense 경계 아래에 그룹화되어 있기 때문에 항상 동시에 함께 “등장”합니다.<Suspense fallback={<Loading />}> <Biography /> <Panel> <Albums /> </Panel> </Suspense>
데이터를 로드하는 컴포넌트가 Suspense 경계의 직접적인 자식일 필요는 없습니다.
예를 들어,
Biography
와 Albums
를 새 Details
컴포넌트로 이동할 수도 있습니다. 이렇게 해도 동작은 달라지지 않습니다. Biography
와 Albums
는 가장 가까운 상위 Suspense 경계를 공유하므로 표시 여부가 함께 조정됩니다.<Suspense fallback={<Loading />}> <Details artistId={artist.id} /> </Suspense> function Panel({ children }) { return ( <section className="panel"> {children} </section> ); } function Details({ artistId }) { return ( <> <Biography artistId={artistId} /> <Panel> <Albums artistId={artistId} /> </Panel> </> ); }
3. 중첩된 콘텐츠가 로드될 때 표시하기
컴포넌트가 일시 중단되면 가장 가까운 상위 Suspense 컴포넌트가 폴백을 표시합니다. 이를 통해 여러 Suspense 컴포넌트를 중첩하여 로딩 시퀀스를 만들 수 있습니다.
각 Suspense boundary의 fallback은 다음 레벨의 콘텐츠를 사용할 수 있게 되면 채워집니다.
예를 들어, 앨범 목록에 자체 fallback을 지정할 수 있습니다:
<Suspense fallback={<BigSpinner />}> <Biography /> <Suspense fallback={<AlbumsGlimmer />}> <Panel> <Albums /> </Panel> </Suspense> </Suspense>
이 변경으로,
Biography
를 표시하기 위해 Albums
가 로드될 때까지 “기다릴” 필요가 없어졌습니다.
순서는 다음과 같습니다Biography
가 아직 로드되지 않은 경우 전체 콘텐츠 영역 대신BigSpinner
가 표시됩니다.
Biography
로드가 완료되면BigSpinner
가 콘텐츠로 대체됩니다.
Albums
가 아직 로드되지 않은 경우Albums
및 그 부모Panel
대신AlbumsGlimmer
가 표시됩니다.
- 마지막으로
Albums
로딩이 완료되면Albums
가AlbumsGlimmer
를 대체합니다.
Suspense boundery 를 사용하면 UI의 어떤 부분이 항상 동시에 “등장”해야 하는지, 어떤 부분이 로딩 상태의 시퀀스에서 점진적으로 더 많은 콘텐츠를 표시해야 하는지 조정할 수 있습니다.
앱의 나머지 동작에 영향을 주지 않고 트리의 어느 위치에서나 Suspense 경계를 추가, 이동, 삭제할 수 있습니다.
그러나, 모든 컴포넌트에 Suspense 경계를 설정하지 마세요. Suspense 경계는 사용자가 경험하게 될 로딩 시퀀스보다 더 세분화되어서는 안 됩니다. 디자이너와 함께 작업하는 경우 로딩 상태를 어디에 배치해야 하는지 디자이너에게 물어보세요. 디자이너가 이미 디자인 와이어프레임에 포함시켰을 가능성이 높습니다.
3. 새 콘텐츠가 로드되는 동안 오래된 콘텐츠 표시하기
아래 예제에서는 검색 결과를 가져오는 동안
SearchResults
컴포넌트가 일시 중단됩니다. "a"
를 입력하고 결과를 기다린 다음 "ab"
를 다시 입력하면 "a"
에 대한 결과는 로딩 fallback으로 대체됩니다.
일반적인 대체 UI 패턴은 목록 업데이트를 연기하고 새 결과가 준비될 때까지 이전 결과를 계속 표시하는 것입니다.
useDeferredValue
훅을 사용하면 쿼리의 지연된 버전을 전달할 수 있습니다:export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
query
가 즉시 업데이트되므로 input에 새 값이 표시됩니다. 그러나 deferredQuery
는 데이터가 로드될 때까지 이전 값을 유지하므로 SearchResults
는 잠시 동안 이전 결과를 표시합니다.
사용자에게 더 명확하게 알리기 위해 이전 결과 목록이 표시될 때 시각적 표시를 추가할 수 있습니다:<div style={{ opacity: query !== deferredQuery ? 0.5 : 1 }}> <SearchResults query={deferredQuery} /> </div>
아래 예제에서
"a"
를 입력하고 결과가 로드될 때까지 기다린 다음 입력을 "ab"
로 변경해 보세요. 이제 새 결과가 로드될 때까지 일시 중단 폴백 대신 이전 결과 목록이 흐리게 표시되는 것을 확인할 수 있습니다:📝Note
지연값과 트랜지션을 모두 사용하면 인라인 표시기를 위해 Suspense 폴백을 표시하지 않을 수 있습니다. 트랜지션은 전체 업데이트를 긴급하지 않은 것으로 표시하므로, 일반적으로 프레임워크 및 라우터 라이브러리에서 탐색을 위해 사용됩니다. 반면 지연값은 주로 UI의 일부를 긴급하지 않은 것으로 표시함으로써, 다른 UI들보다 “지연”시키고자 할 때 유용합니다.
4. 이미 표시된 콘텐츠가 숨겨지지 않도록 방지하기
컴포넌트가 일시 중단되면 가장 가까운 상위 Suspense 경계가 fallback으로 전환됩니다. 이미 일부 콘텐츠가 표시되고 있는 경우 사용자 경험이 끊길 수 있습니다. 아래 예제의 버튼을 눌러 보세요:
버튼을 누르자
Router
컴포넌트가 IndexPage
대신 ArtistPage
를 렌더링했습니다. ArtistPage
내부의 컴포넌트가 일시 중단되었기 때문에 가장 가까운 Suspense 경계가 폴백을 표시하기 시작했습니다. 가장 가까운 Suspense 경계는 루트 근처에 있었기 때문에 전체 사이트 레이아웃이 BigSpinner
로 대체되었습니다.이를 방지하려면
startTransition
을 사용하여 탐색 state 업데이트를 트랜지션으로 표시할 수 있습니다:function Router() { const [page, setPage] = useState('/'); function navigate(url) { startTransition(() => { setPage(url); }); } // ...
이 방법은 React에게 state 전환이 긴급하지 않으며 이미 표시된 콘텐츠를 숨기는 대신 이전 페이지를 계속 표시하는 것이 낫다고 알려줍니다. 이제 버튼을 클릭하면
Biography
가 로드될 때까지 “대기” 상태가 됩니다
트랜지션은 모든 콘텐츠가 로드될 때까지 기다리지 않습니다. 오직 이미 표시된 콘텐츠가 숨겨지지 않을 만큼만 기다립니다.
예를 들어, 웹사이트
Layout
이 이미 표시된 경우 이를 다시 로딩 스피너 뒤로 숨기는 것은 좋지 않을 것입니다. 그러나 Albums
주위의 중첩된 Suspense
경계는 새로운 것이므로, 트랜지션은 이를 기다리지 않습니다.📝Note
Suspense가 도입된 라우터는 기본적으로 탐색 업데이트를 트랜지션으로 감싸고 있을 것입니다.
5. 트랜지션이 발생하고 있음을 나타내기
startTransition
을 isPending
이라는 불리언 값을 제공하는 useTransition
으로 대체하면 됩니다. 6. 탐색시 Suspense 경계 재설정하기
트랜지션하는 동안 React는 이미 표시된 콘텐츠를 숨기지 않습니다. 하지만 다른 매개변수가 있는 경로로 이동하는 경우 React에게 다른 콘텐츠라고 알려줄 필요가 있습니다.
Key
를 사용하여 표현할 수 있습니다:<ProfilePage key={queryParams.id} />
어떤 사용자의 프로필 페이지를 둘러보던 중에 무언가가 일시 중단되었다고 가정해 봅시다. 해당 업데이트가 트랜지션으로 감싸져 있으면 이미 표시된 콘텐츠에 대한 폴백이 촉발되지 않을 것입니다. 이는 예상되는 동작입니다.
이번에는 두 개의 서로 다른 사용자 프로필 사이를 탐색하고 있다고 가정해 봅시다. 이 경우에는 폴백을 표시하는 것이 좋을 것입니다. 예를 들어, 한 사용자의 타임라인은 다른 사용자의 타임라인과 다른 콘텐츠입니다.
Key
를 지정하면 React가 서로 다른 사용자의 프로필을 서로 다른 컴포넌트로 취급하고 탐색 중에 Suspense 경계를 재설정하도록 할 수 있습니다. Suspense 통합 라우터는 이 작업을 자동으로 수행해야 합니다.7. 서버 오류 및 서버 전용 콘텐츠에 대한 fallback 제공하기
스트리밍 서버 렌더링 API 중 하나 (또는 이에 의존하는 프레임워크)를 사용하는 경우, React는 서버에서 발생하는 오류를 처리하기 위해 바운더리도 사용합니다. 컴포넌트가 서버에서 에러를 발생시키더라도 React는 서버 렌더링을 중단하지 않습니다. 대신, 그 위에 있는 가장 가까운 컴포넌트를 찾아서 생성된 서버 HTML에 그 폴백(예: 스피너)을 포함시킵니다. 사용자는 처음에 스피너를 보게 됩니다.
클라이언트에서 React는 동일한 컴포넌트를 다시 렌더링하려고 시도합니다. 클라이언트에서도 에러가 발생하면 React는 에러를 던지고 가장 가까운 error boundary를 표시합니다. 그러나 클라이언트에서 에러가 발생하지 않는다면, 결국 콘텐츠가 성공적으로 표시되었기 때문에 React는 사용자에게 에러를 표시하지 않습니다.
이를 이용하여 일부 컴포넌트를 서버에서 렌더링하지 않도록 선택할 수 있습니다. 이렇게 하려면 서버 환경에서 오류를 발생시킨 다음 해당 컴포넌트를
<Suspense>
경계로 감싸서 해당 HTML을 폴백으로 대체하면 됩니다:<Suspense fallback={<Loading />}> <Chat /> </Suspense> function Chat() { if (typeof window === 'undefined') { throw Error('Chat should only render on the client.'); } // ... }
서버 HTML에는 로딩 표시기가 포함됩니다. 이 표시기는 클라이언트에서는
Chat
컴포넌트로 대체됩니다.문제 해결
업데이트 중에 UI가 폴백으로 대체되는 것을 방지하려면 어떻게 해야 할까요?
표시되는 UI를 폴백으로 대체하면 사용자 환경이 불안정해집니다. 이는 업데이트로 인해 컴포넌트가 일시 중단되었는데 가장 가까운 Suspense 경계에는 이미 콘텐츠가 표시되고 있을 때 발생할 수 있습니다.
이런 일이 발생하지 않도록 하려면
startTransition
을 사용하여 업데이트를 긴급하지 않은 것으로 표시하세요. 트랜지션이 진행되는 동안 React는 원치 않는 폴백이 나타나지 않기에 충분한 데이터가 로드될 때까지 기다립니다function handleNextPageClick() { // If this update suspends, don't hide the already displayed content // 이 업데이트가 일시 중단되어도 이미 표시된 콘텐츠를 숨기지 않습니다 startTransition(() => { setCurrentPage(currentPage + 1); }); }
이렇게 하면 기존 콘텐츠가 숨겨지지 않습니다. 반면 새로 렌더링된
Suspense
경계는 여전히 UI를 가리지 않기 위해 즉시 폴백을 표시하며, 콘텐츠는 사용할 수 있게 될 때 노출합니다.
React는 긴급하지 않은 업데이트 중에만 원치 않는 폴백을 방지합니다. 긴급한 업데이트의 결과인 경우 렌더링을 지연시키지 않습니다.
startTransition
또는 useDeferredValue
와 같은 API를 사용해야 합니다.
라우터가 Suspense와 통합되어 있는 경우, 라우터는 업데이트를
startTransition
으로 자동으로 감싸야 합니다.