- <Suspnese>를 사용하면 자식이 로딩을 완료할 때까지 폴백을 표시할 수 있다.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
Reference
Props
- children : 렌더링하려는 실제 UI이다. 렌더링하는 동안 children이 일시 중단되면 Suspense 경계가 fallback 렌더링으로 전환된다.
- fallback : 로딩이 완료되지 않은 경우에 실제 UI 대신 렌더링할 대체 UI이다. 유효한 어떤 React 노드든 상관없다.
- Suspense는 children이 일시 중단되면 자동으로 fallback으로 전환되고, 데이터가 다시 준비되면 다시 children으로 전환된다.
- 렌더링 중에 fallback이 일시 중단되면 가장 가까운 상위 Suspense 경계가 활성화된다.
-> "가장 가까운(부모) Suspense 경계를 활성화시킬 수 있다"는 말은 Suspense 경계가 중첩되어 있을 때, 하위 경계에서 발생한 `fallback` 상태가 상위 경계에 영향을 미칠 수 있다는 것을 의미한다.
다시 말해, 자식 컴포넌트에서 발생한 `fallback` 상태가 상위 컴포넌트로 전파될 수 있고, 상위 컴포넌트도 `fallback` 상태로 전환될 수 있다는 것을 의미합니다.
즉, 만약 하위 컴포넌트에서 데이터를 로딩하고 그 로딩이 실패하거나 또는 다른 이유로 인해 `fallback` 상태로 전환된다면, 상위 컴포넌트도 이를 감지하고 자체적으로 `fallback` 상태로 전환할 수 있다. 이런 방식으로 사용자에게 더 나은 로딩 및 에러 처리를 제공할 수 있다.
Caveats(주의사항)
- React는 처음 마운트하기 전에 일시 중단되 렌더링의 state를 보존하지 않는다. 즉, 컴포넌트가 로드되면 React는 일시 중단된 트리의 렌더링을 처음부터 다시 시도한다.
Revealing content together at once(콘텐츠를 한 번에 드러내기)
- Suspense 내부의 전체 트리는 기본적으로 단일 단위로 취급된다.
- 내부 트리 구조의 컴포넌트 중 하나만 데이터 대기를 위해 일시 중단하더라도 모든 컴포넌트가 함께 로딩 표시기로 대체된다.
- 데이터를 로드하는 컴포넌트가 Suspense 경계의 직접적인 자식일 필요는 없다.
<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>
function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}
- 위 예제에서, Biography와 Albums 컴포넌트가 데이터를 로딩하는 컴포넌트라고 가정했을 때 Detail 컴포넌트의 자식, 즉 Suspense의 경계의 직접적인 자식이 아니더라도, Suspense의 fallback은 동작한다.
Revealing nested content as it loads(중첩된 콘텐츠가 로드될 때 표시하기)
- Suspense의 경우, 트리 구조내의 컴포넌트가 일시 중단되면 가장 가까운 Suspense 컴포넌트의 fallback이 표시된다.
- 따라서, 중첩 Suspense 컴포넌트를 사용하면 로딩 시퀀스를 만들수 있다.
<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>
- 위 예제 코드의 실행 순서를 보자.
- 1. Biography 컴포넌트가 아직 로드되지 않은 경우, 전체 콘텐츠의 영역을 대신해서 BigSpinner 컴포넌트가 표시된다.
- 2. Biography 컴포넌트가 로드되면, 화면에 표시되고, 만약 Album 컴포넌트가 로드되지 않았다면, 전체 콘텐츠 영역에서 Biography 영역을 제외하고 Panel, Albums 컴포넌트 대신 AlbumsGlimmer 컴포넌트가 표시된다.
- 3. Albums 컴포넌트가 로드되면, 전체 컨텐츠 영역이 표시된다.
- Suspense의 경계를 활용하면, UI의 어떤 부분이 항상 동시에 표시되는지, 로딩 상태의 시퀀스에서 점진적으로 더 많은 콘텐츠를 표시해야 하는지 조정할 수 있다.
- 다만, Suspense 경계는 사용자가 경험하게 될 로딩 시퀀스에 따라 사용해야 하는 것을 지향하며, 모든 컴포넌트에서 Suspense 경계를 설정하는 것은 지양하자.
Showing stale content while fresh content is loading(새 콘텐츠가 로드되는 동안 오래된 콘텐츠 표시하기)
- 일반적인 대체 UI 패턴에서는 새로운 목록을 업데이트 하는 경우, 새로운 목록이 준비가 될때까지 이전 결과를 계속 표시한다.
- 이때, useDeferredValue 훅을 사용하면 쿼리의 연기된(?) 버전을 전달할 수 있다.
- 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>
</>
);
}
- 위 예제 코드에서, input에 새로운 값이 표시되기 전까지 fallback의 <h2>태그가 아닌 deferredQuery가 이전 값을 유지하기 때문에 SearchResults 컴포넌트는 잠시동안 이전 결과를 표시한다.
Preventing already revealed content from hiding(이미 표시된 콘텐츠가 숨겨지지 않도록 방지하기)
- 컴포넌트가 일시중지 되어 가장 가까운 상위 Suspense의 fallback으로 전환될 때, 이미 일부 콘텐츠가 표시되는 경우에는 사용자 경험이 끊길 수가 있다.
import { Suspense, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
setPage(url);
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
- 위 예제에서는 버튼 클릭시에 Router 컴포넌트가 IndexPage 대신 ArtistPage를 렌더링하면서, ArtistPage 내부 컴포넌트가 일시중지 되고 가장 가까운 Suspense 경계가 fallback을 표시한다. 이때, Layout 컴포넌트 역시 BigSpinner로 대체된다.
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
- 이때 위와 같이 startTransition을 사용하여 탐색 state 업데이트를 트랜지션으로 표시할 수 있다.
- startTransition : UI를 차단하지 않고 state를 업데이트할 수 있다(비동기 작업을 수행할 때 사용)(추후 정리 업데이트 예정)
- 즉, React에게 state의 전환(setPage)가 긴급하지 않으며 이미 표시된 콘텐츠를 숨기는 대신 이전 페이지를 계속 표시하는 것이 낫다고 알려주는 방법이다.
- 트랜지션은 모든 콘텐츠가 로드될 때까지 기다리는 것이 아닌, 이미 표시된 콘텐츠가 숨겨지지 않을 만큼만 기다린다.
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';
export default function ArtistPage({ artist }) {
return (
<>
<h1>{artist.name}</h1>
<Biography artistId={artist.id} />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums artistId={artist.id} />
</Panel>
</Suspense>
</>
);
}
function AlbumsGlimmer() {
return (
<div className="glimmer-panel">
<div className="glimmer-line" />
<div className="glimmer-line" />
<div className="glimmer-line" />
</div>
);
}
- 즉, 위 예제에서 버튼이 눌리고 Albums 컴포넌트가 로드되기 전까지 화면에 표시되는 것은 ArtistPage 컴포넌트 내부의 Suspense의 fallback이 되는 것이다.(BigSpinner가 아니다)
Indicating that a transition is happening(트랜지션이 발생하고 있음을 나타내기)
- useTransition 훅의 isPending이라는 불리언 값을 사용하면, 트랜지션이 진행되는 동안 "진행중"이라는 것을 사용자에게 시각적으로 보여줄 수 있다.
import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
// useTransition 사용
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
// isPending 사용
<Layout isPending={isPending}>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
// Layout.js
export default function Layout({ children, isPending }) {
return (
<div className="layout">
<section className="header" style={{
// isPending 값에 따른 시각적 표시
opacity: isPending ? 0.7 : 1
}}>
Music Browser
</section>
<main>
{children}
</main>
</div>
);
}
Resetting Suspense boundaries on navigation(탐색시 Suspense 경계 재설정하기)
- key를 사용하면, React가 서로 다른 컴포넌트로 취급하고 탐색 중에 Suspense 경계를 재설정하도록 할수 있다.
- 예를들어, 어떤 사용자의 프로필 페이지를 둘러보던 중 무언가가 일시 중단되었을 때, 해당 업데이트가 트랜지션으로 감싸져 있으면 이미 표시된 콘텐츠의 fallback은 촉발되지 않을 것이다.
- 하지만, 두 개의 서로 다른 사용자 프로필 사이를 탐색하고 있을 때, 다른 사용자의 프로필을 탐색한다면 fallback을 표시해야하는데, 이때 key를 사용할 수 있다.
<ProfilePage key={queryParams.id} />
Providing a fallback for server errors and server-only content(서버 오류 및 서버 전용 콘텐츠에 대한 폴백 제공하기)
- 스트리밍 서버 렌더링 API 중하나를 사용하는 경우, React는 서버에서 발생하는 오류를 처리하기 위해 Suspense 바운더리를 사용한다.
- 이때, React는 서버 렌더링을 중지시키지 않고 Suspense 컴포넌트의 fallback을 렌더링한다.
- 클라이언트에서는 React의 동일한 컴포넌트를 다시 렌더링하려고 시도하고, 만약 클라이언트에서도 에러가 발생하면 React는 에러를 던지고 가장 가까운 에러 경계를 표시한다.
How do I prevent the UI from being replaced by a fallback during an update?(업데이트 중에 UI가 폴백으로 대체되는 것을 방지하려면 어떻게 해야 할까요?)
- 표시되는 UI를 fallback으로 대체하면 사용자 환경이 불안정하기 때문에, startTransition을 활용하면 충분한 데이터가 로드될 때까지 기다릴 수 있다.
* 참고 : React 공식문서(https://react-ko.dev/learn)
'이것저것 스터디📚 > React - 공식 문서' 카테고리의 다른 글
React 공식문서 -useRef (0) | 2023.09.20 |
---|---|
React 공식문서 -lazy (0) | 2023.09.14 |
React 공식문서 -memo (0) | 2023.09.07 |
React 공식문서 -useContext (0) | 2023.09.06 |
React 공식문서 -useReducer (0) | 2023.09.06 |