Posts Suspense
Post
Cancel

Suspense

https://react-ko.dev/reference/react/Suspense

자식이 로딩을 완료할 때까지 fallback을 표시한다

1
2
3
<Suspense fallback={<Loading/>}>
   <SomeComponent/>
</Suspense>

Props

  • children : 렌더링하려는 실제 UI. 렌더링 동안 children이 일시중단되면 Suspense 경계가 fallback으로 전환된다.
  • fallback : children이 일시중단되는 동안 대체할 UI. fallback 또한 중단되면 가장 가까운 상위 Suspense가 활성화된다.

Caveats

  • React는 처음 마운트 하기 전에 일시 중단된 렌더링의 state를 보존하지 않는다. 컴포넌트 로드 후 일시 중단된 렌더링을 처음부터 다시 시도하다.
  • Suspense가 콘텐츠를 표시하고 있다가 다시 일시 중단되면, 그것이 startTransition이나 useDeferredValue로 인한 것이 아닌 경우 fallback이 다시 표시된다.
  • React가 다시 일시 중단되어 콘텐츠를 숨겨야하는 경우, 콘텐츠 트리에서 useLayoutEffect를 클린업한다. 이후 다시 표시할 준비가 되면 useLayoutEffect를 다시 실행함으로써 콘텐츠가 숨겨져있는 동안 DOM 레이아웃을 측정하는 Effect가 해당 작업을 시도하지 않도록 한다.
  • React에는 Suspense와 통합된 스트리밍 서버 렌더링 및 선택적 Hydration과 같은 최적화가 포함되어 있다.

Suspense 발동 조건

오직 Suspense를 도입한 데이터 소스에서만 Suspense 를 활성화할 수 있다.

  • Relay 및 next.js와 같은 Suspense 도입 프레임워크를 사용한 데이터 패칭
  • lazy를 사용한 지연 로딩 컴포넌트 코드
  • use를 사용해서 Promise 값 읽기

Effect나 이벤트 핸들러 내부에서 데이터 패칭을 하는 경우는 감지하지 않는다. 프레임워크를 제외한 Suspense의 사용은 현재로썬 지원되지 않는다.

Usage

Revealing content together at once

Suspense 아래 컴포넌트는 그 중 하나만 일시 중단되어도 전체가 fallback으로 대체된다.

1
2
3
4
5
6
<Suspense fallback={<Loading />}>
    <Biography artistId={artist.id} />
    <Panel>
	    <Albums artistId={artist.id} />
    </Panel>
</Suspense>

Revealing nested content as it loads

컴포넌트가 일시 중단되면 가장 가까운 Suspense가 fallback을 표시한다. 이를 중첩하여 로딩 시퀀스를 만들 수 있다.

1
2
3
4
5
6
7
8
<Suspense fallback={<BigSpinner />}>  
	<Biography />  
	<Suspense fallback={<AlbumsGlimmer />}>  
		<Panel>  
			<Albums />  
		</Panel>  
	</Suspense>  
</Suspense>
  • Biography가 완료되면 먼저 표시하고, Album이 완료될 때까지 두번째 Suspense는 자신의 fallback을 표시한다.

Showing stale content while fresh content is loading

useDeferredValue을 사용하여 업데이트를 연기하고 fallback 대신 이전 결과를 계속 표시할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style=>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

  • startTransition도 마찬가지로 할 수 있다.

Preventing already revealed content from hiding

startTransition을 사용하여 업데이트 발생 시 이미 표시된 콘텐츠를 fallback 대신 계속 표시할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { Suspense, startTransition, 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) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist=
      />
    );
  }
  return (
    <Layout>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

This post is licensed under CC BY 4.0 by the author.