Suspense
children
의 렌더링이 완료될 때까지 fallback
을 표시할 수 있습니다.
Props
- children: 렌더링하고자 하는 UI이며,
children
의 렌더링이 완료되지 않았다면fallback
으로 렌더링이 전환됩니다. - fallback: 렌더링이 완료되지 않은 경우 실제 UI 대신 렌더링할 대체 UI입니다.
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
주의사항
-
초기 렌더링 전 상태 보존 없음: 처음 마운트되기 전 suspended 되고 다시 렌더링을 시도하면 처음부터 다시 렌더링합니다. (중단 시점을 알 수 없습니다.)
-
Suspense에서의 Fallback 동작:
children
이 다시 suspended 상태가 되면fallback
이 다시 표시됩니다.startTransition
이나useDeferredValue
에 의한 것이라면fallback
이 다시 표시되지 않을 수 있습니다. -
숨겨진 콘텐츠의 layoutEffect 관리:
children
이 다시 suspended 상태가 되면layoutEffect
의clean up
이 실행됩니다. -
React의 최적화 기능:
Suspense
와 통합된 Streaming Server Rendering과 Selective Hydration과 같은 여러 최적화 기능을 제공합니다.
주요 사용법
useSuspenseQuery
import { Suspense } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
function SearchList() {
const { data } = useSuspenseQuery({
queryKey: ['searchItems'],
queryFn: () => API.searchItems(),
});
return (
<>
{data.map((item) => (
<SearchItem key={item.id} item={item} />
))}
</>
);
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<SearchList />
</Suspense>
);
}
useSuspenseQuery
는Suspense
를 잘 지원합니다.- 데이터 페칭의 성공 상태에만 집중합니다.
- 로딩 상태는
Suspense
에서 선언적으로 관리합니다. - 에러 처리의 경우
Errorboundary
를 활용할 수 있습니다.
React.use
import React, { Suspense } from 'react';
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
return response.json();
};
const DataList = () => {
const data = React.use(fetchData());
return (
<ul>
{data.map((post: { id: number; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
const App = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<DataList />
</Suspense>
);
};
export default App;
- 로딩 상태는
Suspense
에서 선언적으로 관리합니다. - 에러 처리의 경우
Errorboundary
를 활용할 수 있습니다.
React.lazy
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const App = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
};
export default App;
- 컴포넌트가 로드될 때까지 대기하는 동안
fallback
을 표시하는 데 사용됩니다.
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>}>
<div style={{ opacity: query !== deferredQuery ? 0.5 : 1 }}>
<SearchResults query={deferredQuery} />
</div>
</Suspense>
</>
);
}
useDeferredValue
를 사용하면 fallback을 보여주지 않고 이전 결과를 계속 보여줍니다.- 이전 상태와 비교할 수 있습니다.
query !== deferredQuery ? 0.5 : 1
useTransition
& Router
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();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage // React.use를 사용하는 컴포넌트
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout isPending={isPending}>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
startTransition
를 사용하면 fallback을 보여주지 않고 이전 결과를 계속 보여줍니다.isPending
를 사용하여 진행 중 상태를 나타낼 수 있습니다.useTransition
을 사용하지 않으면fallback
이 노출됩니다.