BlogReactSuspense

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 상태가 되면 layoutEffectclean 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>
  );
}
  • useSuspenseQuerySuspense를 잘 지원합니다.
  • 데이터 페칭의 성공 상태에만 집중합니다.
  • 로딩 상태는 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이 노출됩니다.

참고 자료

React Docs | Suspense