<Suspense/>, <ErrorBoundary/>, <ErrorBoundaryGroup/> ๋ฑ์ ์ ๊ณตํฉ๋๋ค. ๋ณ ๋ค๋ฅธ ๋ ธ๋ ฅ์์ด ์ฝ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋จ์ํ React์ ๊ฐ๋ ์ ํ์ฅํ ๊ฒ์ ๋๋ค. <Suspense/>, <ErrorBoundary/>, <ErrorBoundaryGroup/>์ ๊ฐ์ React ๊ฐ๋ฐ์์๊ฒ ์น์ํ ์ด๋ฆ์ผ๋ก ์ปดํฌ๋ํธ๋ค์ ์ ๊ณตํฉ๋๋ค.
Suspensive๋ ๊ฐ๋ฐ์๊ฐ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง ํ๊ฒฝ์์๋ React Suspense๋ฅผ ์ ์ง์ ์ผ๋ก ์ฑํํ ์ ์๋๋ก clientOnly๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ํ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ TanStack Query๋ก Suspense ์์ด ์ฝ๋๋ฅผ ์์ฑํ๋ค๋ฉด ์ด๋ ๊ฒ ์์ฑํฉ๋๋ค.
์ด ๊ฒฝ์ฐ isLoading
๊ณผ isError
๋ฅผ ์ฒดํฌํ์ฌ ๋ก๋ฉ๊ณผ ์๋ฌ ์ํ๋ฅผ ์ฒ๋ฆฌํ๊ณ ํ์
์คํฌ๋ฆฝํธ์ ์ผ๋ก data์์ undefined
๋ฅผ ์ ๊ฑฐํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ๋ง์ฝ ์กฐํํด์ผ ํ api๊ฐ ๋ ๋ง์์ง๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค.
์กฐํํด์ผ ํ๋ API๊ฐ ๋ ๋ง์์ง๋ค๋ฉด ์ด ๋ก๋ฉ์ํ์ ์๋ฌ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ์ฝ๋๊ฐ ๋์ฑ ๋ณต์กํด์ง๋๋ค.
Suspense๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ ์ ์ผ๋ก ์ฝ๋๊ฐ ๊ฐ๊ฒฐํด์ง๋๋ค. ํ์ง๋ง ์ปดํฌ๋ํธ์ ๊น์ด๋ ๊น์ด์ง ์ ๋ฐ์ ์์ต๋๋ค.
useSuspenseQuery
๋ Suspense
์ ErrorBoundary
๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ถ์์ ๋ก๋ฉ๊ณผ ์๋ฌ ์ํ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
ํ์ง๋ง useSuspenseQuery
๋ hook์ด๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ์ Suspense
์ ErrorBoundary
๋ฅผ ๋๊ธฐ ์ํด ์ปดํฌ๋ํธ๊ฐ ๋ถ๋ฆฌ๋์ด์ผ๋ง ํ๊ธฐ ๋๋ฌธ์ ๋์ค๊ฐ ๊น์ด์ง๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
Suspensive์ SuspenseQuery ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฉด hook์ ์ ์ฝ์ ํผํด ๊ฐ์ ๋์ค์์ ๋์ฑ ์ฝ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
SuspenseQuery
๋ฅผ ์ฌ์ฉํ๋ฉด depth๋ฅผ ์ ๊ฑฐํ ์ ์์ต๋๋ค.- UserInfo๋ผ๋ ์ปดํฌ๋ํธ๋ฅผ ์ ๊ฑฐํ๊ณ UserProfile๊ณผ ๊ฐ์ Presentational ์ปดํฌ๋ํธ๋ง ๋จ์ผ๋ฏ๋ก ํ ์คํธํ๊ธฐ ์ฌ์์ง๋๋ค.
const Page = () => {const userQuery = useQuery(userQueryOptions())const postsQuery = useQuery({...postsQueryOptions(),select: (posts) => posts.filter(({ isPublic }) => isPublic),})const promotionsQuery = useQuery(promotionsQueryOptions())if (userQuery.isLoading ||postsQuery.isLoading ||promotionsQuery.isLoading) {return 'loading...'}if (userQuery.isError || postsQuery.isError || promotionsQuery.isError) {return 'error'}return (<Fragment><UserProfile {...userQuery.data} />{postsQuery.data.map((post) => (<PostListItem key={post.id} {...post} />))}{promotionsQuery.data.map((promotion) => (<Promotion key={promotion.id} {...promotion} />))}</Fragment>)}
์ด๊ฒ์ด ์ฐ๋ฆฌ๊ฐ Suspensive๋ฅผ ๋ง๋๋ ์ด์ ์ ๋๋ค.
Suspense, ClientOnly, DefaultProps
Next.js์ ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ณด๋ฉด ์๋ฒ์์ Suspense๋ฅผ ์ฌ์ฉํ๊ธฐ ์ด๋ ค์ธ ๋๊ฐ ์์ต๋๋ค.
ํน์ ์๋ฒ์์ Suspense
๋ฅผ ์ฌ์ฉํ๊ณ ์ถ์ง ์์ ๋๊ฐ ์์ต๋๋ค.
์ด๋ด ๋ Suspensive์ ClientOnly๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ํด๊ฒฐํ ์ ์์ต๋๋ค.
ClientOnly
๋ฅผ ๊ฐ์ธ์ฃผ๊ธฐ๋ง ํ๋ฉด ํด๊ฒฐ๋ฉ๋๋ค.
ํน์ Suspensive์ Suspense์๋ clientOnly prop์ ํ์ฉํด ์ด๋ฌํ ๊ฒฝ์ฐ๋ฅผ ์ฝ๊ฒ ๋์ํ ์ ์์ต๋๋ค.
๊ฐ๋จํ์ฃ ?
๊ทธ๋ฐ๋ฐ ๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด Suspense์ ์ผ์ผ์ด fallback์ ๋ฃ์ด์ฃผ๊ธฐ ์ด๋ ค์ธ ๋๊ฐ ์์ต๋๋ค.
ํนํ Admin๊ณผ ๊ฐ์ ์ ํ์ ํ ๋ ๋์์ด๋๊ฐ ์ผ์ผ์ด ์ง์ ํด์ฃผ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ์์ด์ ๊ธฐ๋ณธ๊ฐ์ ์ฃผ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค.
๊ทธ๋ด ๋ DefaultProps
๋ฅผ ํ์ฉํด๋ณด์ธ์.
๊ธฐ๋ณธ fallback์ด ๋ฐ๋ก ๋์ค๋ ๊ฒ๋ณด๋ค๋ FadeIn๊ณผ ๊ฐ์ ํจ๊ณผ๋ฅผ ์ฃผ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค.
๊ทธ๋ ๋ค๋ฉด FadeIn
์ ํ์ฉํด๋ณด๋ฉด ์ด๋จ๊น์?
๋น์ฐํ ๊ธฐ๋ณธ fallback์ Overrideํ๊ณ ์ถ๋ค๋ฉด ๊ทธ๋ฅ ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค.
๋์์ด๋๊ฐ ์ด ๋ถ๋ถ์ ๊ธฐ๋ณธ Spinner
๋ณด๋ค Skeleton
์ผ๋ก ๋์ํ๋๋ก ์ง์ํด๋ฌ๋ผ๊ณ ํ๋ค์~! ๊ทธ๋ฅ ๋ฃ์ผ๋ฉด ๋์ด์.
const Page = () => (<Suspense fallback={<Spinner />}><SuspenseQuery {...notNeedSEOQueryOptions()}>{({ data }) => <NotNeedSEO {...data} />}</SuspenseQuery></Suspense>)
ErrorBoundaryGroup, ErrorBoundary
ErrorBoundary์ fallback ์ธ๋ถ์์ ErrorBoundary๋ฅผ resetํ๊ณ ์ถ์ ๋ resetKeys๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
์ด๋ ๊น์ ์ปดํฌ๋ํธ์ ๊ฒฝ์ฐ resetKey
๋ฅผ ์ ๋ฌํด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ํ state๋ฅผ ๋ง๋ค์ด resetKey
๋ฅผ ์ ๋ฌํด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
Suspensive๊ฐ ์ ๊ณตํ๋ ErrorBoundary์ ErrorBoundaryGroup์ ์กฐํฉํ๋ฉด ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ๋งค์ฐ ๋จ์ํ ํด๊ฒฐํ ์ ์์ต๋๋ค.
ErrorBoundaryGroup
์ ์ฌ์ฉํด๋ณด์ธ์.
๊ทธ๋ฐ๋ฐ ErrorBoundary๋ฅผ ์ฌ์ฉํ๋ค๋ณด๋ฉด ํน์ Error์ ๋ํด์๋ง ์ฒ๋ฆฌํ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค.
๊ทธ๋ด ๋์๋ Suspensive๊ฐ ์ ๊ณตํ๋ ErrorBoundary
์ shouldCatch
๋ฅผ ์จ๋ณด์ธ์. ์ด shouldCatch
์ Error Constructor๋ฅผ ๋ฃ์ผ๋ฉด ํด๋น Error์ ๋ํด์๋ง ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
ํน์ ๋ฐ๋๋ก ๊ทธ Error๋ง ๋นผ๊ณ ์ฒ๋ฆฌํ ์๋ ์์ต๋๋ค.
๊ทธ๋ด ๋์๋ shouldCatch
์ callback์ ๋ฃ์ด์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
const Page = () => {const [resetKey, setResetKey] = useState(0)return (<Fragment><button onClick={() => setResetKey((prev) => prev + 1)}>error reset</button><ErrorBoundary resetKeys={[resetKey]} fallback="error"><ThrowErrorComponent /></ErrorBoundary><DeepComponent resetKeys={[resetKey]} /></Fragment>)}const DeepComponent = ({ resetKeys }) => (<ErrorBoundary resetKeys={resetKeys} fallback="error"><ThrowErrorComponent /><ErrorBoundary resetKeys={resetKeys} fallback="error"><ThrowErrorComponent /></ErrorBoundary></ErrorBoundary>)