<Suspense>
<Suspense>
๋ ์์ ์์๊ฐ ๋ก๋๋๊ธฐ ์ ๊น์ง ํ๋ฉด์ ๋์ฒด UI๋ฅผ ๋ณด์ฌ์ค๋๋ค.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
- ๋ ํผ๋ฐ์ค
- ์ฌ์ฉ๋ฒ
- ์ฝํ ์ธ ๊ฐ ๋ก๋๋๋ ๋์ ๋์ฒด UI ๋ณด์ฌ์ฃผ๊ธฐ
- ์ฝํ ์ธ ๋ฅผ ํ๊บผ๋ฒ์ ํจ๊ป ๋ณด์ฌ์ฃผ๊ธฐ
- ์ค์ฒฉ๋ ์ฝํ ์ธ ๊ฐ ๋ก๋๋ ๋ ๋ณด์ฌ ์ฃผ๊ธฐ
- ์ ์ฝํ ์ธ ๊ฐ ๋ก๋๋๋ ๋์ ์ด์ ์ฝํ ์ธ ๋ณด์ฌ์ฃผ๊ธฐ
- ์ด๋ฏธ ๋ณด์ธ ์ฝํ ์ธ ๊ฐ ์จ๊ฒจ์ง์ง ์๋๋ก ๋ฐฉ์ง
- Transition์ด ๋ฐ์ํ๊ณ ์์์ ๋ณด์ฌ์ฃผ๊ธฐ
- Navigation์์ Suspense ์ฌ์ค์ ํ๊ธฐ
- ์๋ฒ ์๋ฌ ๋ฐ ์๋ฒ ์ ์ฉ ์ฝํ ์ธ ์ ๋ํ Fallback ์ ๊ณต
- ๋ฌธ์ ํด๊ฒฐ
๋ ํผ๋ฐ์ค
<Suspense>
Props
children
: ๊ถ๊ทน์ ์ผ๋ก ๋ ๋๋งํ๋ ค๋ ์ค์ UI์ ๋๋ค.children
์ ๋ ๋๋ง์ด ์ง์ฐ๋๋ฉด, Suspense๋fallback
์ ๋์ ๋ ๋๋งํฉ๋๋ค.fallback
: ์ค์ UI๊ฐ ๋ก๋๋๊ธฐ ์ ๊น์ง ๋์ ๋ ๋๋ง ๋๋ ๋์ฒด UI์ ๋๋ค. ์ฌ๋ฐ๋ฅธ React node ํ์์ ๋ฌด์์ด๋ ๋์ฒด UI๋ก ํ์ฉํ ์ ์์ง๋ง, ์ค์ ๋ก๋ ๋ณดํต ๋ก๋ฉ ์คํผ๋๋ ์ค์ผ๋ ํค์ฒ๋ผ ๊ฐ๋จํ placeholder๋ฅผ ํ์ฉํฉ๋๋ค. Suspense๋children
์ ๋ ๋๋ง์ด ์ง์ฐ๋๋ฉด ์๋์ผ๋กfallback
์ผ๋ก ์ ํํ๊ณ , ๋ฐ์ดํฐ๊ฐ ์ค๋น๋๋ฉดchildren
์ผ๋ก ๋ค์ ์ ํํฉ๋๋ค. ๋ง์ฝfallback
์ ๋ ๋๋ง์ด ์ง์ฐ๋๋ฉด, ๊ฐ์ฅ ๊ฐ๊น์ด ๋ถ๋ชจ Suspense๊ฐ ํ์ฑํ๋ฉ๋๋ค.
์ฃผ์ ์ฌํญ
- React๋ ์ปดํฌ๋ํธ๊ฐ ์ฒ์์ผ๋ก ๋ง์ดํธ ๋๊ธฐ ์ ์ ์ง์ฐ๋ ๋ ๋๋ง์ ํ๋ ๋์์ ์ด๋ค state๋ ์ ์งํ์ง ์์ต๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ก๋๋๋ฉด React๋ ์ผ์ ์ค์ง๋ ํธ๋ฆฌ๋ฅผ ์ฒ์๋ถํฐ ๋ค์ ๋ ๋๋งํฉ๋๋ค.
- Suspense๊ฐ ํธ๋ฆฌ์ ์ฝํ
์ธ ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์์ ๋ ๋๋ค์ ์ง์ฐ๋๋ฉด
startTransition
๋useDeferredValue
๋ก ์ธํ ์ ๋ฐ์ดํธ๊ฐ ์๋ ํ,fallback
์ด ๋ค์ ๋ณด์ ๋๋ค. - React๊ฐ ๋ค์ ์ผ์ ์ค์ง๋์ด ๋ณด์ด๋ ์ฝํ ์ธ ๋ฅผ ์จ๊ฒจ์ผ ํ๋ ๊ฒฝ์ฐ, ์ฝํ ์ธ ํธ๋ฆฌ์์ layout Effect๋ค์ ์ ๋ฆฌํฉ๋๋ค. ์ฝํ ์ธ ๊ฐ ๋ค์ ๋ณด์ผ ์ค๋น๊ฐ ๋๋ฉด React๋ layout Effect๋ค์ ๋ค์ ์คํํฉ๋๋ค. ์ด๋ก์จ DOM ๋ ์ด์์์ ์ธก์ ํ๋ Effect๊ฐ ์ฝํ ์ธ ๊ฐ ์จ๊ฒจ์ ธ ์๋ ๋์ ๋์ํ์ง ์๋๋ก ๋ณด์ฅํฉ๋๋ค.
- React๋ Suspense์ ํตํฉ๋ Streaming Server Rendering์ Selective Hydration๊ฐ์ ๋ด๋ถ ์ต์ ํ๋ฅผ ํฌํจํ๊ณ ์์ต๋๋ค. ์ํคํ ์ฒ ๊ฐ์๋ฅผ ์ฝ๊ณ ๊ธฐ์ ๊ฐ์ฐ์ ์์ฒญํ์ฌ ๋ ์์ธํ ์์๋ณด์ธ์.
์ฌ์ฉ๋ฒ
์ฝํ ์ธ ๊ฐ ๋ก๋๋๋ ๋์ ๋์ฒด UI ๋ณด์ฌ์ฃผ๊ธฐ
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ๊ณณ์ Suspense ๊ฒฝ๊ณ๋ก ๊ฐ์ ์ ์์ต๋๋ค.
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>
React๋ children์ ํ์ํ ๋ชจ๋ ์ฝ๋์ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง loading fallback์ ๋ณด์ฌ์ค๋๋ค.
์๋ ์์์์๋ ์จ๋ฒ ๋ชฉ๋ก์ ๊ฐ์ ธ์ค๋ ๋์ ์จ๋ฒ
์ปดํฌ๋ํธ๊ฐ ์ง์ฐ๋ฉ๋๋ค. ๋ ๋๋งํ ์ค๋น๊ฐ ๋ ๋๊น์ง ๊ฐ์ฅ ๊ฐ๊น์ด Suspense๋ fallback, ์ฆ Loading
์ปดํฌ๋ํธ๋ฅผ ํ์ํฉ๋๋ค. ๋ฐ์ดํฐ๊ฐ ๋ชจ๋ ๋ก๋๋๋ฉด React๋ Loading
fallback์ ์จ๊ธฐ๊ณ ๋ก๋๋ ๋ฐ์ดํฐ๋ก ์จ๋ฒ
์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค.
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>; }
์ฝํ ์ธ ๋ฅผ ํ๊บผ๋ฒ์ ํจ๊ป ๋ณด์ฌ์ฃผ๊ธฐ
๊ธฐ๋ณธ์ ์ผ๋ก Suspense ๋ด๋ถ์ ์ ์ฒด ํธ๋ฆฌ๋ ํ๋์ ๋จ์๋ก ์ทจ๊ธ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด๋ฌํ ๊ตฌ์ฑ ์์ ์ค ํ๋๋ผ๋ ์ด๋ค ๋ฐ์ดํฐ์ ์ํด ์ง์ฐ๋๋๋ผ๋ ๋ชจ๋ ๊ตฌ์ฑ ์์๊ฐ ํจ๊ป ๋ก๋ฉ ํ์๋ก ๋์ฒด๋ฉ๋๋ค.
<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>
๊ทธ๋ฐ ๋ค์ ๋ชจ๋ ๋ณด์ผ ์ค๋น๊ฐ ๋๋ฉด ํ๊บผ๋ฒ์ ๋ชจ๋ ํจ๊ป ๋ณด์ ๋๋ค.
์๋ ์์์์๋ Biography
์ Albums
๋ชจ๋ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ์ง๋ง ๋ ๊ตฌ์ฑ ์์๋ ๊ฐ์ ๋จ์ผ Suspense ์๋์ ๊ทธ๋ฃนํ๋์ด ์๊ธฐ ๋๋ฌธ์ ํญ์ ๋์์ ํจ๊ป ๊ทธ๋ ค์ง๊ฒ ๋ฉ๋๋ค.
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> <Suspense fallback={<Loading />}> <Biography artistId={artist.id} /> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </> ); } function Loading() { return <h2>๐ Loading...</h2>; }
๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ ์ปดํฌ๋ํธ๊ฐ Suspense์ ์ง์ ์ ์ธ ์์์ผ ํ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, Biography
์ Albums
๋ฅผ ์๋ก์ด Details
์ปดํฌ๋ํธ๋ก ์ด๋ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํด๋ ๋์์ ๋ณ๊ฒฝ๋์ง ์์ต๋๋ค. Biography
์ Albums
๋ ๊ฐ์ฅ ๊ฐ๊น์ด ์์ Suspense๋ฅผ ๊ณต์ ํ๋ฏ๋ก ๋ ์ปดํฌ๋ํธ์ ๋
ธ์ถ ์ฌ๋ถ๋ ํจ๊ป ์กฐ์ ๋ฉ๋๋ค.
<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>
function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}
์ค์ฒฉ๋ ์ฝํ ์ธ ๊ฐ ๋ก๋๋ ๋ ๋ณด์ฌ ์ฃผ๊ธฐ
์ปดํฌ๋ํธ๊ฐ ์ผ์ ์ค๋จ๋๋ฉด ๊ฐ์ฅ ๊ฐ๊น์ด ์์ Suspense ์ปดํฌ๋ํธ๊ฐ Fallback์ ๋ณด์ฌ์ค๋๋ค. ์ด๋ฅผ ํตํด ์ฌ๋ฌ Suspense ์ปดํฌ๋ํธ๋ฅผ ์ค์ฒฉํ์ฌ ๋ก๋ฉ ์์๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ๊ฐ Suspense์ 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
๊ฐ ๋ก๋ฉ์ ์๋ฃํ๋ฉดAlbumsGlimmer
๋ฅผ ๋์ฒดํฉ๋๋ค.
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> <Suspense fallback={<BigSpinner />}> <Biography artistId={artist.id} /> <Suspense fallback={<AlbumsGlimmer />}> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </Suspense> </> ); } function BigSpinner() { return <h2>๐ Loading...</h2>; } function AlbumsGlimmer() { return ( <div className="glimmer-panel"> <div className="glimmer-line" /> <div className="glimmer-line" /> <div className="glimmer-line" /> </div> ); }
Suspense๋ฅผ ์ฌ์ฉํ๋ฉด UI์ ์ด๋ค ๋ถ๋ถ์ด ํญ์ ๋์์ ๊ทธ๋ ค์ ธ์ผ ํ๋์ง, ์ด๋ค ๋ถ๋ถ์ด ๋ก๋ฉ ์์์์ ์ ์ง์ ์ผ๋ก ๋ ๋ง์ ์ฝํ ์ธ ๋ฅผ ๋ณด์ฌ์ค์ผ ํ๋์ง ์กฐ์ ํ ์ ์์ต๋๋ค. ์ฑ์ ๋๋จธ์ง ๋์์ ์ํฅ์ ์ฃผ์ง ์๊ณ ํธ๋ฆฌ์ ์ด๋ ์์น์์๋ Suspense๋ฅผ ์ถ๊ฐ, ์ด๋ ๋๋ ์ญ์ ํ ์ ์์ต๋๋ค.
๋ชจ๋ ์ปดํฌ๋ํธ ์ฃผ์์ Suspense๋ฅผ ๋์ง ๋ง์ธ์. Suspense๋ ์ฌ์ฉ์๊ฐ ๊ฒฝํํ๊ธฐ๋ฅผ ์ํ๋ ๋ก๋ฉ ์์๋ณด๋ค ๋ ์ธ๋ถํ๋์ด์๋ ์ ๋ฉ๋๋ค. ๋์์ด๋์ ํจ๊ป ์์ ํ๋ ๊ฒฝ์ฐ ๋ก๋ฉ ์ํ๋ฅผ ์ด๋์ ๋ฐฐ์นํด์ผ ํ๋์ง ๋์์ด๋์๊ฒ ๋ฌผ์ด๋ณด์ธ์. ๋์์ด๋๊ฐ ์ด๋ฏธ ๋์์ธ ์์ด์ด ํ๋ ์์ ํฌํจํ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
์ ์ฝํ ์ธ ๊ฐ ๋ก๋๋๋ ๋์ ์ด์ ์ฝํ ์ธ ๋ณด์ฌ์ฃผ๊ธฐ
์ด ์์ ์์๋ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ SearchResults
์ปดํฌ๋ํธ๊ฐ ์ง์ฐ๋ฉ๋๋ค. "a"
๋ฅผ ์
๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฐ ๋ค์ "ab"
๋ก ๋ฐ๊ฟ๋ณด์ธ์. "a"
์ ๋ํ ๊ฒฐ๊ณผ๋ ๋ก๋ฉ Fallback์ผ๋ก ๋ฐ๋๋๋ค.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
์ผ๋ฐ์ ์ธ ๋์ฒด UI ํจํด์ ๋ชฉ๋ก๋ค์ ๋ํ ์
๋ฐ์ดํธ๋ฅผ ์ฐ๊ธฐํ๊ณ ์ ๊ฒฐ๊ณผ๊ฐ ์ค๋น๋ ๋๊น์ง ์ด์ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์
๋๋ค. useDeferredValue
Hook์ ์ฌ์ฉํ๋ฉด ์ฟผ๋ฆฌ์ ์ง์ฐ๋ ๋ฒ์ ์ ์๋๋ก ์ ๋ฌํ ์ ์์ต๋๋ค.
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
๋ ์ฆ์ ์
๋ฐ์ดํธ๋๋ฏ๋ก ์
๋ ฅ์ ์ ๊ฐ์ด ํ์๋ฉ๋๋ค. ๊ทธ๋ฌ๋ deferredQuery
๋ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ์ด์ ๊ฐ์ ์ ์งํ๋ฏ๋ก SearchResults
๋ ์ ์ ๋์ ์ด์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
์ฌ์ฉ์์๊ฒ ๋ ๋ช ํํ๊ฒ ์๋ฆฌ๊ธฐ ์ํด ์ด์ ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ํ์๋ ๋ ์๊ฐ์ ํ์๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>
์๋ ์์์์ "a"
๋ฅผ ์
๋ ฅํ๊ณ ๊ฒฐ๊ณผ๊ฐ ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ๋ค์ ์
๋ ฅ์ "ab"
๋ก ํธ์งํด๋ณด์ธ์. ์ด์ ์ ๊ฒฐ๊ณผ๊ฐ ๋ก๋๋ ๋๊น์ง Suspense Fallback ๋์ ํฌ๋ฏธํ ์ด์ ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ํ์๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
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={{ opacity: isStale ? 0.5 : 1 }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
์ด๋ฏธ ๋ณด์ธ ์ฝํ ์ธ ๊ฐ ์จ๊ฒจ์ง์ง ์๋๋ก ๋ฐฉ์ง
์ปดํฌ๋ํธ๊ฐ ์ง์ฐ๋๋ฉด ๊ฐ์ฅ ๊ฐ๊น์ด ์์ 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์ ๋ณด์ฌ์ฃผ๊ธฐ ์์ํ์ต๋๋ค. ๊ฐ์ฅ ๊ฐ๊น์ด Suspense๊ฐ root ๊ทผ์ฒ์ ์์๊ธฐ ๋๋ฌธ์ ์ ์ฒด ์ฌ์ดํธ ๋ ์ด์์์ด BigSpinner
๋ก ๋์ฒด๋์์ต๋๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด startTransition
์ ์ฌ์ฉํ์ฌ navigation state ์
๋ฐ์ดํธ๋ฅผ transition์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
์ด๋ state ์ ํ์ด ๊ธํ์ง ์์ผ๋ฉฐ, ์ด๋ฏธ ๊ณต๊ฐ๋ ์ฝํ
์ธ ๋ฅผ ์จ๊ธฐ๋ ๋์ ์ด์ ํ์ด์ง๋ฅผ ๊ณ์ ํ์ํ๋ ๊ฒ์ด ์ข๋ค๋ ๊ฒ์ React์๊ฒ ์๋ ค์ค๋๋ค. ์ด์ ๋ฒํผ์ ํด๋ฆญํ๋ฉด Biography
๊ฐ ๋ก๋๋ ๋๊น์ง โ๋๊ธฐโํฉ๋๋ค:
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={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout> {content} </Layout> ); } function BigSpinner() { return <h2>๐ Loading...</h2>; }
transition์ ๋ชจ๋ ์ฝํ
์ธ ๊ฐ ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์์ต๋๋ค. ์ด๋ฏธ ๋ณด์ฌ์ง ์ฝํ
์ธ ๊ฐ ์จ๊ฒจ์ง์ง ์๋๋ก ์ถฉ๋ถํ ์ค๋ ๊ธฐ๋ค๋ฆฝ๋๋ค. ์๋ฅผ ๋ค์ด ์น์ฌ์ดํธ Layout
์ ์ด๋ฏธ ๋ณด์ด๋ฏ๋ก ๋ก๋ฉ ์คํผ๋ ๋ค์ ์จ๊ธฐ๋ ๊ฒ์ ์ข์ง ์์ ๊ฒ์
๋๋ค. ๊ทธ๋ฌ๋ Albums
์ฃผ์์ ์ค์ฒฉ๋ Suspense
๋ ์๋ก์ด ๊ฒ์ด๋ฏ๋ก transition์ด ๊ธฐ๋ค๋ฆฌ์ง ์์ต๋๋ค.
Transition์ด ๋ฐ์ํ๊ณ ์์์ ๋ณด์ฌ์ฃผ๊ธฐ
์์ ์์์์๋ ๋ฒํผ์ ํด๋ฆญํด๋ navigation์ด ์งํ ์ค์ด๋ผ๋ ์๊ฐ์ ํ์๊ฐ ์์ต๋๋ค. ํ์๊ธฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด startTransition
์ boolean ๊ฐ์ธ isPending
๊ฐ์ ์ ๊ณตํ๋ useTransition
์ผ๋ก ๋ฐ๊พธ๋ฉด ๋ฉ๋๋ค. ์๋ ์์์์๋ transition์ด ์งํ๋๋ ๋์ ์น์ฌ์ดํธ ํค๋ ์คํ์ผ์ ๋ณ๊ฒฝํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
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 artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout isPending={isPending}> {content} </Layout> ); } function BigSpinner() { return <h2>๐ Loading...</h2>; }
Navigation์์ Suspense ์ฌ์ค์ ํ๊ธฐ
Transition์ด ์งํ๋๋ ๋์ React๋ ์ด๋ฏธ ๋ณด์ธ ์ฝํ
์ธ ๋ฅผ ์จ๊ธฐ์ง ์์ต๋๋ค. ํ์ง๋ง ๋ค๋ฅธ ๋งค๊ฐ๋ณ์๊ฐ ์๋ ๊ฒฝ๋ก๋ก ์ด๋ํ๋ ๊ฒฝ์ฐ React์ ๋ค๋ฅธ ์ฝํ
์ธ ๋ผ๊ณ ์๋ ค์ฃผ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์ด๋ฅผ key
๋ก ํํํ ์ ์์ต๋๋ค.
<ProfilePage key={queryParams.id} />
์ฌ์ฉ์์ ํ๋กํ ํ์ด์ง ๋ด์์ ์ด๋ ์ค์ธ๋ฐ ๋ฌด์ธ๊ฐ๊ฐ ์ง์ฐ๋์๋ค๊ณ ๊ฐ์ ํด ๋ณด์ธ์. ํด๋น ์ ๋ฐ์ดํธ๊ฐ transition์ผ๋ก ๊ฐ์ธ์ ธ ์์ผ๋ฉด ์ด๋ฏธ ํ์๋ ์ฝํ ์ธ ์ ๋ํ Fallback์ด ํธ๋ฆฌ๊ฑฐ๋์ง ์์ต๋๋ค. ์ด๊ฒ์ด ์์๋๋ ๋์์ ๋๋ค.
ํ์ง๋ง ์ด์ ๋ ๊ฐ์ ์๋ก ๋ค๋ฅธ ์ฌ์ฉ์ ํ๋กํ ์ฌ์ด๋ฅผ ์ด๋ํ๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ด ๊ฒฝ์ฐ Fallback์ ํ์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์๋ฅผ ๋ค์ด ํ ์ฌ์ฉ์์ ํ์๋ผ์ธ์ด ๋ค๋ฅธ ์ฌ์ฉ์์ ํ์๋ผ์ธ๊ณผ ๋ค๋ฅธ ์ฝํ
์ธ ๋ผ๊ณ , ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. key
๋ฅผ ์ง์ ํ๋ฉด React๊ฐ ์๋ก ๋ค๋ฅธ ์ฌ์ฉ์์ ํ๋กํ์ ์๋ก ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ก ์ทจ๊ธํ๊ณ ํ์ํ๋ ๋์ Suspense๋ฅผ ์ฌ์ค์ ํ๋๋ก ํ ์ ์์ต๋๋ค. Suspense ํตํฉ ๋ผ์ฐํฐ๋ ์ด ๋์์ ์๋์ผ๋ก ์ํํด์ผ ํฉ๋๋ค.
์๋ฒ ์๋ฌ ๋ฐ ์๋ฒ ์ ์ฉ ์ฝํ ์ธ ์ ๋ํ Fallback ์ ๊ณต
์คํธ๋ฆฌ๋ฐ ์๋ฒ ๋ ๋๋ง API ์ค ํ๋(๋๋ ์ด์ ์์กดํ๋ ํ๋ ์์ํฌ)๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, React๋ ์๋ฒ์ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด <Suspense>
๋ฐ์ด๋๋ฆฌ๋ ์ฌ์ฉํ ๊ฒ์
๋๋ค. ์ปดํฌ๋ํธ๊ฐ ์๋ฒ์์ ์๋ฌ๋ฅผ ๋ฐ์์ํค๋๋ผ๋ React๋ ์๋ฒ ๋ ๋๋ง์ ์ค๋จํ์ง ์์ต๋๋ค. ๋์ , ๊ทธ ์์ ์๋ ๊ฐ์ฅ ๊ฐ๊น์ด <Suspense>
์ปดํฌ๋ํธ๋ฅผ ์ฐพ์์ ๊ทธ Fallback(์: ์คํผ๋)์ ์์ฑ๋ ์๋ฒ HTML์ ํฌํจํฉ๋๋ค. ์ฌ์ฉ์๋ ์ฒ์์๋ ์คํผ๋๋ฅผ ๋ณด๊ฒ ๋ฉ๋๋ค.
ํด๋ผ์ด์ธํธ์์ React๋ ๋์ผํ ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ ๋๋งํ๋ ค๊ณ ์๋ํฉ๋๋ค. ํด๋ผ์ด์ธํธ์์๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด React๋ ์๋ฌ๋ฅผ ๋์ง๊ณ ๊ฐ์ฅ ๊ฐ๊น์ด error boundary๋ฅผ ํ์ํฉ๋๋ค. ๊ทธ๋ฌ๋ ํด๋ผ์ด์ธํธ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด ์ฝํ ์ธ ๊ฐ ๊ฒฐ๊ตญ ์ฑ๊ณต์ ์ผ๋ก ๋ณด์๊ธฐ ๋๋ฌธ์ React๋ ์ฌ์ฉ์์๊ฒ ์๋ฌ๋ฅผ ๋ณด์ฌ์ฃผ์ง ์์ต๋๋ค.
์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ผ๋ถ ์ปดํฌ๋ํธ๋ฅผ ์๋ฒ์์ ๋ ๋๋งํ์ง ์๋๋ก ์ ํํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ ค๋ฉด ์๋ฒ ํ๊ฒฝ์์ ์๋ฌ๋ฅผ ๋ฐ์์ํจ ๋ค์ <Suspense>
๋ฐ์ด๋๋ฆฌ๋ก ๊ฐ์ธ์ ํด๋น HTML์ Fallback์ผ๋ก ๋์ฒดํฉ๋๋ค.
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}
์๋ฒ HTML์ ๋ก๋ฉ UI๊ฐ ํฌํจ๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ์์๋ Chat
์ปดํฌ๋ํธ๋ก ๋์ฒด๋ฉ๋๋ค.
๋ฌธ์ ํด๊ฒฐ
์ ๋ฐ์ดํธ ์ค์ UI๊ฐ Fallback์ผ๋ก ๋์ฒด๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ๋์?
ํ์๋๋ UI๋ฅผ Fallback์ผ๋ก ๋์ฒดํ๋ฉด ์ฌ์ฉ์ ํ๊ฒฝ์ด ๋ถ์์ ํด์ง๋๋ค. ์ด๋ ์ ๋ฐ์ดํธ๋ก ์ธํด ์ปดํฌ๋ํธ๊ฐ ์ง์ฐ๋๊ณ ๊ฐ์ฅ ๊ฐ๊น์ด Suspense๊ฐ ์ด๋ฏธ ์ฌ์ฉ์์๊ฒ ์ฝํ ์ธ ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์์ ๋ ๋ฐ์ํ ์ ์์ต๋๋ค.
์ด๋ฐ ์ผ์ด ๋ฐ์ํ์ง ์๋๋ก ํ๋ ค๋ฉด, startTransition
์ ์ฌ์ฉํ์ฌ ์
๋ฐ์ดํธ๋ฅผ ๊ธด๊ธํ์ง ์์ ๊ฒ์ผ๋ก ์ฒ๋ฆฌํ์ธ์. transition์ด ์งํ๋๋ ๋์ React๋ ์์น ์๋ Fallback์ด ๋ํ๋์ง ์๋๋ก ์ถฉ๋ถํ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.
function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
์ด๋ ๊ฒ ํ๋ฉด ๊ธฐ์กด ์ฝํ
์ธ ๊ฐ ์จ๊ฒจ์ง์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์๋ก ๋ ๋๋ง ๋ Suspense
๋ ์ฌ์ ํ ์ฆ์ Fallback์ ๋ณด์ฌ์ค์ UI๋ฅผ ์ฐจ๋จํ์ง ์๊ณ ์ฌ์ฉ์๊ฐ ์ฝํ
์ธ ๋ฅผ ์ด์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
React๋ ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ ์ค์๋ง ์์น ์๋ Fallback์ ๋ฐฉ์งํฉ๋๋ค. ๊ธด๊ธํ ์
๋ฐ์ดํธ์ ๊ฒฐ๊ณผ์ธ ๊ฒฝ์ฐ ๋ ๋๋ง์ ์ง์ฐ์ํค์ง ์์ต๋๋ค. startTransition
๋๋ useDeferredValue
์ ๊ฐ์ API๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
Router๊ฐ Suspense์ ํตํฉ๋ ๊ฒฝ์ฐ, router๋ ์
๋ฐ์ดํธ๋ฅผ ์๋์ผ๋ก startTransition
์ ๋ํํด์ผ ํฉ๋๋ค.