๐ฅ 16. ๋น๋๊ธฐ UX์ ํจ๋ฌ๋ค์ ์ ํ (Suspense & ErrorBoundary)
๐ ๊ฐ์
๋ก๋ฉ๊ณผ ์๋ฌ ์ฒ๋ฆฌ ์ฑ ์์ ๋ถ๋ชจ ๋ ์ด์ด๋ก ์๋ฒฝํ ๋ ๋๊ธฐ๊ณ , ์์ ์ปดํฌ๋ํธ๋ '๋ฐ์ดํฐ๊ฐ 100% ์กด์ฌํ ๋์ ์ฑ๊ณตํ UI'๋ง ๋จ๊ฒจ๋๋ ๊ทน๊ฐ์ ์ ์ธ์ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๊ธฐ๋ฒ์ ๋ฐฐ์๋๋ค.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
if (isLoading)๊ณผif (isError)๋ก ๋๋ฐฐ๋ ๋ฌธ์ ๊ฐ ํฐ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌ์ถํ ์ ์๋ค.- ์ปดํฌ๋ํธ์ ์ฑ ์์ "๋ฐ์ดํฐ ๋ ๋๋ง" ํ๋๋ก๋ง ๊ทน๋จ์ ์ผ๋ก ์ขํ๋ ์ ์ธ์ (Declarative) ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ง๋ฒ์ ์ดํดํ๋ค.
- ์ฑ ์ด๋์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ์ฑ ์ ์ฒด๊ฐ ํ์๊ฒ ์ฌ๋ผ์ง์ง ์๊ณ (ํ์ดํธ์์), ์ฐ์ํ๊ฒ ๋ถ๋ถ ์๋ฌ ํ๋ฉด์ ๋์์ฃผ๋ ์ํคํ ์ฒ๋ฅผ ์ค๊ณํ ์ ์๋ค.
๐ ๋ชฉ์ฐจ
- ๐ค ์ ์์์ผ ํ๋๊ฐ: ๋ช ๋ นํ ๋ก๋ฉ/์๋ฌ ์ฒ๋ฆฌ์ ์ฅ์
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ ๊ทน์ ์ฒด๋: ์ ์ธ์ ๋น๋๊ธฐ UX ์ค๊ณํ๊ธฐ
- ๐ (์ฌํ ๋ณด๋์ค) Next.js App Router์์ ๋ง๋จ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 12๋ถ / ํต์ฌ ํํธ: 7๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์(PM):"์์ฒ ๋, ์ ์ ํ๋กํ ํ์ด์ง ๋ค์ด๊ฐ์ ๋ ๋ก๋ฉ ๋น๊ธ๋น๊ธ ๋๋ ์คํผ๋ ๋ง์ด์์. ์ง๊ธ์ ์ ์ฒด ํ๋ฉด์ ๊ฐ๋ฆฌ๋๋ฐ, ํ๋กํ ์ฌ์ง ์ชฝ๋ง ๋ฐ๋ก ๋๊ณ , ์์ฑํ ๊ธ ๋ชฉ๋ก ์ชฝ ๋ฐ๋ก ๋๊ฒ ์ค๋ฌด์คํ๊ฒ ๋ง๋ค์ด์ฃผ์ค ์ ์๋์? ์์ฐธ, ๊ธ ๋ชฉ๋ก ๋ถ๋ฌ์ค๋ค ์๋ฌ ๋๋ฉด ๊ฑฐ๊ธด '๋ค์ ์๋' ๋ฒํผ ๋ฌ์์ฃผ์๊ณ ์!"
- ์์ฒ (์ ์
):"๋ค? ๊ทธ๋ผ
ProfileAvatar์ปดํฌ๋ํธ ์์๋isLoading,isError์ํ ๋ง๋ค๊ณ ...PostList์ปดํฌ๋ํธ ์์๋isLoading,isError์ํ ๋ง๋ค์ด์ ๋คif (isLoading) return <Spinner/>๋ฅผ ๋ฃ์ด์ผ๊ฒ ๋ค์... ์ฝ๋๊ฐ 3๋ฐฐ๋ก ๋ถ์ด๋ ํ ๋ฐ ใ ใ " - ์ํธ(๋ฆฌ๋): "์์ฒ ๋, ๋ก๋ฉ๊ณผ ์๋ฌ ์กฐ๊ฑด๋ฌธ์ด ์ปดํฌ๋ํธ ์์ ๋ง์ด ํฉ์ด์ ธ ์๊ตฐ์. ์ปดํฌ๋ํธ๋ '๋ฐ์ดํฐ๊ฐ ์ค๋ฉด ์ด๋ป๊ฒ ์์๊ฒ ๊ทธ๋ฆด๊น?' ๋จ ํ๋์๋ง ์ง์คํด์ผ ํฉ๋๋ค. ์๋ฌ์ ๋ก๋ฉ ์ฒ๋ฆฌ๋ ์์ ๊ฒฝ๊ณ๋ก ์ฌ๋ฆฌ์ธ์. Suspense ์ ErrorBoundary ๋ก ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค."
๐ค ์ ์์์ผ ํ๋๊ฐ: ๋ช ๋ นํ ๋ก๋ฉ/์๋ฌ ์ฒ๋ฆฌ์ ์ฅ์
1~3๋ ์ฐจ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ค์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ญ์คํ๊ตฌ ์๋์ ๊ฐ์ ํํ๋ฅผ ๋๋๋ค.
// โ ์์ฒ ์ด์ ํํ ๋ช
๋ นํ ๋น๋๊ธฐ ์ปดํฌ๋ํธ (๊ด์ฌ์ฌ ํผ์ฌ)
function UserProfile({ userId }) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchUserData(userId)
.then(res => setData(res))
.catch(err => setError(err))
.finally(() => setIsLoading(false));
}, [userId]);
// ๐จ UI๋ฅผ ๊ทธ๋ฆฌ๋ ๋ณธ์ฐ์ ์๋ฌด ์ ์ ๋ฐฉ์ด๋ง(์กฐ๊ฑด๋ฌธ)์ด ๋๋ฌด๋๋ ๋ง๋ค!
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage message={error.message} />;
// ์ฌ๊ธฐ๊น์ง ์ด์๋จ์์ผ ๊ฒจ์ฐ ์ง์ง ๋ชฉ์ (UI ๋ ๋๋ง) ๋ฌ์ฑ ๊ฐ๋ฅ
return <div>ํ์ํฉ๋๋ค, {data.name} ๋!</div>;
}์ด ๋ฐฉ์์ ์น๋ช ์ ์ธ ๋ฌธ์ ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๊ด์ฌ์ฌ ์ค์ผ: ์ปดํฌ๋ํธ์ ๋ณธ์ง์ "๋ฐ์ดํฐ๋ฅผ ์์๊ฒ UI๋ก ๋งตํํ๋ ๊ฒ"์ธ๋ฐ, ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๊ฐ์ ธ์ฌ์ง, ์คํจํ๋ฉด ์ด๋กํ ์ง ์จ๊ฐ ์ก์ผ์ ์๋ฌ๋ฆฝ๋๋ค.
- **"์ด ๋ฐ์ดํฐ๋ ์ง์ง ์๋๊ฐ?":**TypeScript๋ก ๋ฌ์๋ด๋
data๋ ์ด๊ธฐ์null์ ๋๋ค. ๋ฐ๋ผ์ ๋ฐ์์data.name์ ์ธ ๋๋ง๋ค ๋ถ์์ฆ์ธ(Optional Chainingdata?.name)์ ์๋ฌ๋ฆฌ๊ฒ ๋ฉ๋๋ค. - UI ๊ธฐํ ๋ณ๊ฒฝ ๋์ ๋ถ๊ฐ: PM์ด "๋ก๋ฉ ๋ฐ๋ฅผ ์ ์์ชฝ ํค๋๋ก ํฉ์ณ์ฃผ์ธ์"๋ผ๊ณ ํ๋ฉด, ๋ก๋ฉ ์ํ๋ฅผ ๋ ๋ถ๋ชจ๋ก ๋์ด์ฌ๋ฆฌ๋ฉฐ(Lifting State Up) ๋๊ณต์ฌ๋ฅผ ํด์ผ ํฉ๋๋ค.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
- **๊ธฐ์กด ๋ฐฉ์ (์์ฒ ์ด ํ๋ฒ๊ฑฐ ๊ฐ๊ฒ):**์๋ฆฌ์ฌ(์ปดํฌ๋ํธ)๊ฐ ํจํฐ๋ฅผ ๊ตฝ๋ค๊ฐ ๊ณ ๊ธฐ๊ฐ ๋จ์ด์ง๋ฉด(๋ก๋ฉ), ์๊ธฐ๊ฐ ์ง์ ์๋ฆฌ๋ฅผ ๋ฉ์ถ๊ณ ํ์ ๋๊ฐ "์๋, ๊ณ ๊ธฐ ์ค๋ ์ค ใ ใ " ํป๋ง์ ๋ญ๋๋ค. ๊ณ ๊ธฐ๊ฐ ์ํ์ผ๋ฉด(์๋ฌ) "์๋ ๊ณ ๊ธฐ ์ํจ!" ํป๋ง์ ๋ค๊ณ ์. ์๋ฆฌ์ ์ง์คํ ์๊ฐ ์์ฃ .
- Suspense & ErrorBoundary ๋ฐฉ์ (์ํธ์ ๊ณ ๊ธ ๋ ์คํ ๋): ์๋ฆฌ์ฌ๋ "๋ ๋ฌด์กฐ๊ฑด ๊ณ ๊ธฐ(๋ฐ์ดํฐ)๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ณ ํ๋ฒ๊ฑฐ ์กฐ๋ฆฝ๋ง ํ๋ค" ๋ผ๊ณ ์ ์ธํฉ๋๋ค.
๋ง์ฝ ๊ณ ๊ธฐ๊ฐ ์์ง ์ ์์ผ๋ฉด? ์๋ฆฌ์ฌ๋ ์ฃผ๋ฐฉ ๋ฐ์ผ๋ก ์๋ฆฌ๋ฅผ ์ง๋ฅด๋ฉฐ ์ฐ๋ฌ์ง๋๋ค(Throw). ๊ทธ๋ผ ๋งค๋์ (Suspense)๊ฐ ํ์ด๋์์ ์๋์๊ฒ ์ฐ์ํ๊ฒ ์์ ๋นต(Spinner)์ ๋์ ํ๋ฉฐ ๊ธฐ๋ค๋ฆฌ๊ฒ ํฉ๋๋ค.
๋ง์ฝ ๊ณ ๊ธฐ๊ฐ ์ํ์ผ๋ฉด? ์ญ์ ์๋ฆฌ์ง๋ฅด๋ฉฐ ์ฐ๋ฌ์ง๊ณ , ์ ์ฅ(ErrorBoundary)์ด ๋ฐ์ด๋์ ์ ์คํ๊ฒ ์ฌ๊ณผ ์ฟ ํฐ(์๋ฌ UI)์ ๋๋ฆฝ๋๋ค. ์๋ฆฌ์ฌ๋ ์ค์ง '์๋ฆฌ'๋ผ๋ ๋ณธ์ง์๋ง ์ง์คํฉ๋๋ค!
์ด๊ฒ์ด ๋ฆฌ์กํธ๊ฐ ๋ฐ๊ณ ์๋ "๋์์ ํจ๊ณผ(Algebraic Effects)" ์ ๋ฉํ ๋ชจ๋ธ์ ๋๋ค. ์์ ์ปดํฌ๋ํธ์์ ๊ฐ๋นํ ์ ์๋ ์ฌ์ด๋ ์ดํํธ(๋ก๋ฉ, ์๋ฌ)๋ฅผ ๋ถ๋ชจ๊ฐ ์บ์นํ์ฌ ๋์ ์ฒ๋ฆฌํ๋ ๊ฒ ์ด์ฃ .
๐งฉ ๊ทน์ ์ฒด๋: ์ ์ธ์ ๋น๋๊ธฐ UX ์ค๊ณํ๊ธฐ
์์ฒ ์ด์ ๋ฌธ์ ๊ฐ ์ปธ๋ ์ฝ๋๋ฅผ Suspense ์ ErrorBoundary (React 18 & Next.js ์๋์ ํ์ค ๊ต์)๋ฅผ ์จ์ ๋ฆฌํฉํ ๋งํด ๋ด
์๋ค. (ํ์
์์๋ React Query์ useSuspenseQuery ๋ฑ๊ณผ ๊ฒฐํฉํด ๋ง์ด ์ฌ์ฉํฉ๋๋ค.)
โ 1๋จ๊ณ: ์ํธ์ ์ ์ธ์ ์ปดํฌ๋ํธ ์ค๊ณ
์ฐ์ ์์ ์ปดํฌ๋ํธ๋ ์๋ฌ๊ณ ๊ฐ์ ํํ์ ๋ค ๋ฒ๋ฆฌ๊ณ ํต์ฌ ๋ฐ์ดํฐ๋ง ๋ณด๊ณ ๋์งํ๋๋ก ๋๋ฅผ ๋น์๋๋ค.
// ๐ฏ 1. ๋๋ฅผ ๋น์ด ์ ์ธ์ ์์ ์ปดํฌ๋ํธ (์ฑ๊ณตํ ๋ฏธ๋๋ง ๋ฐ๋ผ๋ณธ๋ค)
function UserProfile({ userId }) {
// isLoading? isError? ๊ทธ๋ฐ ๊ฑด ๋ถ๋ชจ๋์ด ์์์ ํ์ญ๋๋ค.
// ๋๋ '๋ฐ์ดํฐ๊ฐ ๋ฌด์กฐ๊ฑด ์๋ฒฝํ๊ฒ ์จ ์ํ'๋ผ๊ณ ๊ฐ์ ํฉ๋๋ค!
const { data } = useSuspenseQuery(fetchUserData, userId);
// optional chaining(data?.name) ๋ฐ์๋ ๊ฐ๋ ์ค๋๋ค. ๋ฐ์ดํฐ๋ ์ธ์ ๋ 100% ์์ต๋๋ค.
return (
<div className="profile-card">
<img src={data.profileImage} />
<h3>ํ์ํฉ๋๋ค, {data.name} ๋!</h3>
</div>
);
}โ 2๋จ๊ณ: ๋งค๋์ (Suspense)์ ์ ์ฅ(ErrorBoundary) ๋ฐฐ์น
์์ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ ๋์ง(Throw) Promise ํ๋ฉ ์ํ์ Error ๋ฆฌ์ ์
์ํ๋ฅผ ๋ถ๋ชจ ๊ณ์ธต์์ ์ก์์ฑ๋๋ค.
// ๐ฏ 2. ๋น๋๊ธฐ ์ฑ
์์ ๋ชจ๋ ๋ ์์ ๋ถ๋ชจ ์ปจํ
์ด๋
import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; // ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ๊ฐ๋ ฅ ๊ถ์ฅ
function UserPage({ userId }) {
return (
<div className="page-layout">
<h1>์ ์ ํ์ด์ง</h1>
{/* ๐ด ์ ์ฅ: ๋ง์ฝ ์์์ ์น๋ช
์ ์๋ฌ๊ฐ ๋๋ฉด ์ด UI๋ก ๋ฎ์ด์์! */}
<ErrorBoundary fallback={<div>ํ๋กํ์ ๋ถ๋ฌ์ค๋ค ์๋ฒ๊ฐ ํฐ์ก์ต๋๋ค ๐ญ</div>}>
{/* ๐ก ๋งค๋์ : ์์์ ์์ง ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์ค์ด๋ฉด ์ด Spinner๋ฅผ ๊ทธ๋ ค์ค! */}
<Suspense fallback={<Spinner size="large" />}>
{/* ์ฑ๊ณตํ ๋ฏธ๋๋ง ๊ทธ๋ฆฌ๋ ์๋ฆฌ์ฌ ์ปดํฌ๋ํธ */}
<UserProfile userId={userId} />
</Suspense>
</ErrorBoundary>
</div>
);
}โ 3๋จ๊ณ: ๊ธฐํ ๋ณ๊ฒฝ? ๋ ๊ณ ์กฐ๋ฆฝํ๋ฏ ์ฑ์น!
"์์ฒ ์จ, ํ๋กํ ๋ง๊ณ ํ๋จ์ ์๋ '์ต๊ทผ ์์ฑ ๊ธ ๋ชฉ๋ก'๋ ๋ฐ๋ก ๋ก๋ฉ ๋๊ฒ ํด์ฃผ์ธ์~"
๊ณผ๊ฑฐ์ ๋ด๋ถ ๋ด๋ถ ์ฝ๋๋ฅผ ํฌ๊ฒ ๊ณ ์ณค๊ฒ ์ง๋ง, ์ง๊ธ์ ์์ ์ปดํฌ๋ํธ๋ค์ ๊ฑด๋๋ฆฌ์ง ์๊ณ ๋ถ๋ชจ์ ๋ ์ด์์ ๊ป๋ฐ๊ธฐ๋ง ํ๋ํ๋ฉด ๋ฉ๋๋ค.
// ๐ฏ 3. ๊ทนํ์ ๋ถ๋ฆฌ ๋์ ์ป๊ฒ ๋ ์๋ฒฝํ UI ๋ ์ด์์ ํต์ ๊ถ
function UserPage({ userId }) {
return (
<>
<ErrorBoundary fallback={<TextError />}>
{/* ํ๋กํ์ ํ๋กํ๋๋ก ๋ฐ๋ก ๋น๊ธ๋น๊ธ ๋๊ณ ~ */}
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile userId={userId} />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<Button>๊ธ ๋ชฉ๋ก ๊ฐฑ์ ์คํจ. ์ฌ์๋</Button>}>
{/* ๊ฒ์๋ฌผ ๋ชฉ๋ก์ ๋ ๊ฑ๋ค๋๋ก ๋ฐ๋ก ๋น๊ธ๋น๊ธ ๋๋๋ค! (๋ณ๋ ฌ ๋ก๋ฉ) */}
<Suspense fallback={<ListSkeleton />}>
<UserPosts userId={userId} />
</Suspense>
</ErrorBoundary>
</>
);
}์ด ๊ตฌ์กฐ ๋๋ถ์ ํน์ ๊ตฌ์ญ(์: ๊ธ ๋ชฉ๋ก)์์ ์๋ฒ ์๋ฌ๊ฐ ์ค๋ฅ๊ฐ ๋๋, ํ๋กํ ๋ ๋๋ง ๋ฐ์ค๋ ๋๋ก์์ด ์ด์๋จ์ต๋๋ค. ํ์ดํธ์์(์ฑ ์ ์ฒด๊ฐ ํ์๊ฒ ์ค๋จ๋๋ ํ์)์ ์๋ฒฝํ ๋ฐฉ์ดํ๋ ๋ถ๋ถ ์ฅ์ ๊ฒฉ๋ฆฌ ์์คํ ์ด ์์ฑ๋ ๊ฒ์ ๋๋ค.
๐ (์ฌํ ๋ณด๋์ค) Next.js App Router์์ ๋ง๋จ
Next.js์ ์ ํ App Router(app/ ๋๋ ํ ๋ฆฌ)๋ ์ด Suspense ์ ErrorBoundary ์ ๊ฐ๋
์ ์ฐ์ฃผ ๋๊น์ง ํ์ผ ์์คํ
์ผ๋ก ๋์ด์ฌ๋ ธ์ต๋๋ค.
- Next.js์์
loading.tsxํ์ผ์ ๋ง๋ค๋ฉด? ๐ ์๋์ผ๋ก ๊ฐ์ฅ ๊ฐ๊น์ดlayout.tsx๋ฐ์<Suspense fallback={<Loading />}>๊ป๋ฐ๊ธฐ๋ฅผ ๋ชฐ๋ ์์์ค๋๋ค! error.tsxํ์ผ์ ๋ง๋ค๋ฉด? ๐ ๋ชฐ๋<ErrorBoundary fallback={<Error />}>๊ป๋ฐ๊ธฐ๋ฅผ ์์์ค๋๋ค!
์ฐ๋ฆฌ๊ฐ ์ฌํ ์์ผ๋ก ์ง๋ ๋ณต์กํ ๋ถ๋ชจ ์ปดํฌ๋ํธ ๋ํ ์ฝ๋๋ฅผ, ๊ทธ์ ํ์ผ์ ์๋ง์ ์์น์ ๋ฐฐ์นํ๋ ๊ฒ๋ง์ผ๋ก ๋ผ์ฐํ ์์ค์์ ๋ํํด ์ฃผ๋ ๊ฒ์ ๋๋ค. (์ด ๊ธฐ๋ฒ๋ค์ด ๋ฑ์ฅํ ๊ทผ๋ณธ ์๋ฆฌ๋ฅผ ์๋ฉด, Next.js ๊ณต์ ๋ฌธ์๊ฐ ์ผ๋ง๋ ํธ์์ฑ์ ๊ทน๋ํํ๋์ง ์ ๋ช ํ๊ฒ ์ฒด๊ฐํ ์ ์์ต๋๋ค.)
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ด์ | ๋ช
๋ นํ ํจ๋ฌ๋ค์ (if (loading)) | ์ ์ธ์ ํจ๋ฌ๋ค์ (Suspense) |
|---|---|---|
| ์๋ฌ/๋ก๋ฉ ์ฒ๋ฆฌ ์ฃผ์ฒด | ์์ ์ปดํฌ๋ํธ ๋ณธ์ธ | ๋๋ฅผ ๊ฐ์ธ๊ณ ์๋ ๋ถ๋ชจ ์ปจํ ์ด๋ ๊ณ์ธต |
| ์ฝ๋์ ๊ด์ฌ์ฌ | ๋ก์ง(Fetch) + ๋ฐฉ์ด๋ง(If๋ฌธ) + ๊ตฌ์กฐ(UI) ๋ค์์ | ์ค์ง ํต์ฌ ๋ฐ์ดํฐ UI ๋ ๋๋ง ํ๋์๋ง ์ง์ค |
| ํ์ ์์ ์ฑ | ์คํฌ๋ฆฐ์ ๋ฐ ๋๊น์ง ๋ฐ์ดํฐ๊ฐ null์ผ ์ ์์ | ๋ ๋๋ง ๋๋ ์๊ฐ ๋ฐ์ดํฐ๋ ๋ฌด์กฐ๊ฑด ์กด์ฌํจ 100% ๋ณด์ฅ |
| ๋ ์ด์์ ์์ ๋ | ๊ธฐํ์ด ๋ฐ๋๋ฉด ๋ก์ง ์ฝ๋ ์ ์ฒด๋ฅผ ๊ฐ์์์ด์ผ ํจ | ์ปดํฌ๋ํธ๋ ๋๋๊ณ ๋ฐ๊นฅ Suspense ์์น๋ง ์กฐ๋ฆฝํ๋ฉด ๋จ |
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
"์ปดํฌ๋ํธ๋ ๋๋ฌผ์ ํ๋ฆฌ์ง ์๋๋ค."
๋ก๋ฉ๊ณผ ์๋ฌ๋ ๋ด๋ถ์์ ์ ๊ฐ๊ฐ ์ฒ๋ฆฌํ์ง ๋ง๊ณ ๋ช ์์ ์ผ๋ก ์์ ๊ฒฝ๊ณ๋ก ์ฌ๋ ค๋ผ(Throw).Suspense์ErrorBoundary๊ฐ ์ฌ์ฉ์๊ฐ ๋ณด๋ ํ๋ฉด ๋จ์๋ก ์์ ์ ์ธ ๊ฒฝ๊ณ๋ฅผ ๋ง๋ค์ด ์ค ๊ฒ์ด๋ค.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. Suspense์ ErrorBoundary๋ฅผ ํ์ฉํ์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ ์ธ์ ์ผ๋ก ๋ถ๋ฆฌํ์ ๋, ์์ ์ปดํฌ๋ํธ(์๋ฆฌ์ฌ)์ ๋ด๋ถ ์ฝ๋ ์ง๋์ด ๊ทน๋จ์ ์ผ๋ก ๊ฐ๋ฒผ์์ง๋ ์ํคํ
์ฒ์ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
- A) ํด๋์ค ์ปดํฌ๋ํธ์ LifeCycle ๋ฉ์๋๋ฅผ Hooks๋ก ์ค๋ฒ๋ผ์ด๋ฉํ๋ฉด์ ์ฝ๋ฐฑ ๊ณผ๋ฐ์ด ํด์ง๊ธฐ ๋๋ฌธ.
- B) ์์ ์ปดํฌ๋ํธ ๋ด๋ถ์ ์กด์ฌํ๋
isLoading,isError์ํ ๋ณ์์ ๊ทธ์ ๋ฐ๋ฅธ ๋ฐฉํจ๋ง์ด ๋ถ๊ธฐ(if) ์ฝ๋๋ค์ด ํต์งธ๋ก ๋ฏ๊ฒจ๋๊ฐ๋ฉฐ ์ปดํฌ๋ํธ์ ์ ์ผํ ์ญํ ์ธ '๋ฐ์ดํฐ ์กด์ฌ ์์ UI ๋งคํ'๋ง ์์ํ๊ฒ ๋จ๊ธฐ ๋๋ฌธ. - C)
Suspense๊ตฌ๋ฌธ ๋ด๋ถ์ ์์๋ค์ React ๊ฐ์ ๋(VDOM) ๋ณํ์ ๊ฑฐ์น์ง ์๊ณ ์ง์ ๋ธ๋ผ์ฐ์ ๋ค์ดํฐ๋ธ ์ฝ๋๋ก C++ ์ปดํ์ผ๋์ด ๊ทธ๋ ค์ง๊ธฐ ๋๋ฌธ.
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค: ์ ํํฉ๋๋ค! ๊ธฐ์กด ํจ๋ฌ๋ค์์์ ๊ฐ๋ฐ์๋ ํ ์ง๋ถ ํ ๊ฐ์กฑ ์์์ "์์ง ์ค์ง ์์ ๋ฐ์ดํฐ", "๋ง๊ฐ์ง ๋ฐ์ดํฐ", "์ ์์ ์ธ ๋ฐ์ดํฐ" 3๊ฐ์ง ํํ์ฐ์ฃผ๋ฅผ ๋ชจ๋ if-else ๋ฌธ์ผ๋ก ์ผ์ดํด์ผ ํ์ต๋๋ค. ํ์ง๋ง ์ฑ
์์ ์์(Throw)ํจ์ผ๋ก์จ ์ปดํฌ๋ํธ๋ ์ค์ง "๋ฐ์ดํฐ๊ฐ ๋์ฐฉํ ์๋ฆ๋ค์ด 1๊ฐ ์ฐ์ฃผ"์ ์ผ๊ตดํ์ ๋ง ๊ทธ๋ฆด ์ ์๊ฒ ๋์๊ณ , ํฐ ๊ฐ๋
์ฑ ์์น์ ์ด๋ค๋์ต๋๋ค.
Q2. ์ฌ๋ด ํ๋ก์ ํธ์์ ๋ง์ดํ์ด์ง๋ฅผ ๋ฆฌํฉํ ๋ง ์ค์
๋๋ค. ์ ์ ํ๋กํ ์น์
๊ณผ ๊ฒฐ์ ๋ด์ญ ์น์
, ๊ทธ๋ฆฌ๊ณ ์ถ์ฒ ์ํ 3๊ฐ์ ํํธ๊ฐ ๋ชจ๋ ๋
๋ฆฝ์ ์ธ ์ธ๋ถ API๋ฅผ ๊ธ์ด์ค๊ณ ์์ต๋๋ค. ๋ค์ ์ค Suspense์ ErrorBoundary๋ฅผ ์ค๊ณํ๋ ์๋์ด์ ๊ฐ์ฅ ๋ฐ๋์งํ ์์ผ๋?
- A) 3๊ฐ์ ์น์
์ ์ ๋ถ ํ๋์ ๊ฑฐ๋ํ
<Suspense>์<ErrorBoundary>๋ก ์ต์๋จ ๋ ์ด์์์ ๋ชฝ๋ ๋ฌถ์ด์ ๋จ์ผ ์์คํ ์ผ๋ก ๋ง๋ ๋ค. (ํ๋๋ผ๋ ๋ก๋ฉ ์ค์ด๋ฉด ํ๋ฉด ์ ์ฒด ํฝ์ด ๋๋ฆฌ๊ธฐ) - B) ๊ฐ 3๊ฐ์ ์ปดํฌ๋ํธ ํ์ผ ์์ ์ง์ ๋ค์ด๊ฐ์
catch๊ตฌ๋ฌธ์ ํ๊ณ , ์คํผ๋ ๋์ฐ๋if์ฝ๋๋ฅผ ์ฑ์ฌ๋ฃ์ด ์๊ธ์์กฑํ๊ฒ ํ๋ค. - C) ๊ฐ ์น์
์ ๊ฐ์ธ๋
<Suspense>3๊ฐ์<ErrorBoundary>3๊ฐ๋ฅผ ๋ถ๋ชจ ๋ ์ด์์์์ ๊ฐ๊ฐ ๋ ๋ฆฝ(๋ณ๋ ฌ)์ ์ผ๋ก ์์์ค๋ค. (ํ๋กํ์ ์ผ์ฐ ๋จ๋ฉด ๋จผ์ ๋ณด์ฌ์ฃผ๊ณ , ๊ฒฐ์ ๋ด์ญ์ด DB ์ฐ๊ฒฐ ๋ฌธ์ ๋ก ์คํจํ๋๋ผ๋ ๋๋จธ์ง ํ์ด์ง๋ ์ ์ ๋ ๋๋ง ๋๋๋ก ์ปดํฌ๋ํธ ์ฌ(Island)์ ๊ตฌ์ถํ๋ค)
โ **์ ๋ต:**C
๐ก ์์ธ ํด์ค: ๋ง์ดํฌ๋ก ์ํคํ ์ฒ์ ์ ์์ ๋๋ค. A ๋ฐฉ์์ ๊ฒฐ์ ๋ด์ญ API ์๋ต์ด ํผ์ 5์ด ๊ฑธ๋ฆด ๊ฒฝ์ฐ, ์ด๋ฏธ 0.1์ด ๋ง์ ๋์ฐฉํ ์ ์ ํ๋กํ์กฐ์ฐจ ์ฌ์ฉ์ ๋์์ ์ ๋ณด์ฌ์ฃผ๋ ์ํํ ์ฒด๊ฐ ๋ณ๋ชฉ(Waterfall bottleneck) ์ ๋ง๋ญ๋๋ค. ๋ฐ๋ฉด C ๋ฐฉ์์ฒ๋ผ ์๊ฒ ๊ฒฉ๋ฆฌ(Colocated Boundary)์ํค๋ฉด ๊ฐ ๊ตฌ์ญ์ด ์๊ธฐ ํ์ด์ค๋๋ก ๋ก๋ฉ ํฝ์ด๋ฅผ ๋๋ฆฌ๋ค ๋ ๋๋ง๋๋ฉฐ, ํ๋๊ฐ ์ค๋ฅ๊ฐ ๋๋ ๋๋จธ์ง 2๊ฐ๋ ํ๋ฉด์ ์ด์๋จ๋ ๊ฐ์ธํ ํ๋ ฅ์ฑ(Resilience) ์ ํ๋ณดํ๊ฒ ๋ฉ๋๋ค.
Q3. ์์ฒ ์ด์ ํ
์คํธ ํ์: ์์๋ค ์ปค๋ฎค๋ํฐ ํ๋กํ ํ์ด์ง์์ ProfileCard, RecentPosts, BadgePanel์ด ๊ฐ๊ฐ ๋ค๋ฅธ API๋ฅผ Suspense ๊ธฐ๋ฐ ํ
์ผ๋ก ์ฝ๊ณ ์์ต๋๋ค. ์์์ด "ํ๋กํ ์ฌ์ง์ ๋จผ์ ๋ณด์ด๊ณ , ์์ฑ ๊ธ ๋ชฉ๋ก๋ง ์คํจํ๋ฉด ๊ทธ ์์ญ์ ์ฌ์๋ ๋ฒํผ์ ๋ณด์ฌ์ฃผ์ธ์"๋ผ๊ณ ์์ฒญํ์ต๋๋ค. ์ํธ๊ฐ ๋ฆฌ๋ทฐ์์ ํต๊ณผ์ํฌ ์ค๊ณ๋ ๋ฌด์์ธ๊ฐ์?
- A) ์ธ ์ปดํฌ๋ํธ๋ฅผ ํ๋์ ์ต์์
<Suspense>์ ํ๋์<ErrorBoundary>๋ก ๊ฐ์ธ์ ํ๋ฉด ์ ์ฒด๊ฐ ํ ๋ฒ์ ์ฑ๊ณตํ๊ฑฐ๋ ํ ๋ฒ์ ์คํจํ๊ฒ ๋ง๋ ๋ค. - B) ๊ฐ ๋ฐ์ดํฐ ์น์
์ ํ์ํ UX ๋จ์๋๋ก ๋ณ๋์
<Suspense>์<ErrorBoundary>๋ก ๊ฐ์ธ๊ณ , ์คํจํ ์์ญ์๋ง fallback๊ณผ ์ฌ์๋ UI๋ฅผ ๋ฐฐ์นํ๋ค. - C)
RecentPosts๋ด๋ถ์์try/catch์isLoading์ํ๋ฅผ ๋ค์ ๋ง๋ค๊ณ , ๋๋จธ์ง ์ปดํฌ๋ํธ๋ Suspense๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค.
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค: Suspense์ ErrorBoundary์ ํต์ฌ์ ๋ก๋ฉ๊ณผ ์คํจ๋ฅผ "์ปดํฌ๋ํธ ๋ด๋ถ ์กฐ๊ฑด๋ฌธ"์ด ์๋๋ผ "์ฌ์ฉ์๊ฐ ๊ฒฝํํ๋ ํ๋ฉด ๊ตฌ์ญ" ๋จ์๋ก ๊ฒฉ๋ฆฌํ๋ ๊ฒ์ ๋๋ค. ์ต์์ ํ๋๋ก ๋ฌถ์ผ๋ฉด ์์ ์คํจ๊ฐ ์ ์ฒด ํ์ด์ง์ ํ์ดํธ์์์ด ๋๊ณ , ์์ ๋ด๋ถ๋ก ๋๋๋ฆฌ๋ฉด ์ ์ธ์ ๋น๋๊ธฐ ๊ตฌ์กฐ์ ์ฅ์ ์ ์์ต๋๋ค. ์ํธ๋ผ๋ฉด API ๋จ์๊ฐ ์๋๋ผ ์ฌ์ฉ์์๊ฒ ๋ ๋ฆฝ์ ์ผ๋ก ๋ณด์ฌ์ค ์ ์๋ UX ๋จ์๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฒฝ๊ณ๋ฅผ ๋๋๋ผ๊ณ ๋ณผ ๊ฒ๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ "๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ฝ๋"์ "์ฑ๊ณตํ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ์ฝ๋"๋ฅผ ํ ์ปดํฌ๋ํธ ์์ ๊ณ์ ์์ด๋ ์ด์ ๋ฅผ ๋ค์ ๋ดค๋ค. ์์ ์ ๋๋ ๋ก๋ฉ๊ณผ ์๋ฌ๋ฅผ ์ง์ ์ฒ๋ฆฌํด์ผ ์ฑ ์๊ฐ ์๋ ์ฝ๋๋ผ๊ณ ์๊ฐํ๋๋ฐ, ์ฌ์ค ๊ทธ๊ฑด ํ๋ฉด ๊ตฌ์ญ๋ง๋ค ์คํจ ๋ฒ์๋ฅผ ์ค๊ณํ์ง ๋ชปํ๋ค๋ ๋ป์ ๊ฐ๊น์ ๋ค.
๐ก "Suspense์ ErrorBoundary๋ ์คํผ๋ ์ฅ์์ด ์๋๋ผ, ๋ก๋ฉ๊ณผ ์คํจ๋ฅผ ์ด๋ UX ๊ฒฝ๊ณ์์ ๊ฒฉ๋ฆฌํ ์ง ๊ฒฐ์ ํ๋ ์ค๊ณ ๋๊ตฌ๋ค."
์ํธ ๋ฆฌ๋ ๋์ด "API ๊ฐ์ ๋ง๊ณ ์ฌ์ฉ์๊ฐ ๋ฐ๋ก ๋ณผ ์ ์๋ ํ๋ฉด ๋จ์๋ก ๊ฒฝ๊ณ๋ฅผ ์ก์๋ณด๋ผ"๊ณ ํ๋ ๋ง์ด ๋จ์๋ค. ๋ด์ผ๋ถํฐ๋ ์ ๋น๋๊ธฐ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๋ ๋จผ์ ์ฑ๊ณต UI, ๋ก๋ฉ fallback, ์คํจ fallback, ์ฌ์๋ ์์น๋ฅผ ํ๋ก ์ ๊ณ ๋์ ์ฝ๋๋ฅผ ์งค ์๊ฐ์ด๋ค. ์ด์ ์์ฒ ์ ์กฐ๊ฑด๋ฌธ์ ์ค์ด๋ ์ฌ๋์ด ์๋๋ผ, ์ฅ์ ๊ฐ ๋ฒ์ง์ง ์๋ ํ๋ฉด ๊ฒฝ๊ณ๋ฅผ ์ค๊ณํ๋ ์ฌ๋์ด ๋์ด์ผ ํ๋ค.