๐ก ๋ฐ์ดํฐ ์์ฒญ ์ํ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋ ธ์ถ๋๋ UI/UX ์ค๊ณ๋ ๋ง์ ๊ณ ๋ฏผ์ ํ์๋ก ํ๋ค. ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด React๋ ๋ค์ํ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋๋ฐ, ๊ทธ ์ค
Suspense์ErrorBoundary๋ฅผ ํ์ฉํ ์ ์ธ์ ๋ฐ์ดํฐ ํจ์นญ์ด ์๋ค.
๋ฐ์ดํฐ ํจ์นญ์ ๋๋ฆ ์ ํต์ ์ผ๋ก(?) ์ปดํฌ๋ํธ ๋ด๋ถ์์ data, loading, error์ ์ํ๋ค์ ์ฌ์ฉํ์ฌ ๋ฐํ๋๋ ์ปดํฌ๋ํธ์ ๋ํ ๋ถ๊ธฐ ์ฒ๋ฆฌ๋ก ์ด๋ฃจ์ด์ก๋ค. ์๋์ ์์ ์ฝ๋๋ ๋น๋๊ธฐ ์์ฒญ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ์ฌ์ฉ์์๊ฒ ์ ๋ฌํ๋ ๊ฐ๋จํ ์ปดํฌ๋ํธ์ด๋ค.
// SampleContents.tsx
const SampleContents = () => {
const [sampleDatas, setSampleDatas] = useState();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
useEffect(() => {
(async () => {
try {
setIsLoading(true); // isLoading ์ํ๋ณ๊ฒฝ
const { data } = await queryFn(); // axios get ์์ฒญ ์์ ํจ์
setSampleDatas(data);
} catch (e) {
setError(e);
} finally {
setIsLoading(false); // ์ฑ๊ณต์ด๋ ์คํจ์ด๋ isLoading์ false
}
})();
}, []);
if (isLoading) {
return <Loader />;
}
if (error) {
return <Error error={error} />;
}
return (
<div>
{sampleDatas.map(data => (
<Content data={data} />
))}
</div>
);
};
export default SampleContents;
๋น๋๊ธฐ ํจ์ queryFn์ด ๋ฐํํ๋ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ค๊ธฐ ์ ๊น์ง isLoading, error ์ํ์ ๋ฐ๋ผ ๋ฐํ๋๋ ์ปดํฌ๋ํธ๋ฅผ ๋ถ๊ธฐ ์ฒ๋ฆฌํ์ฌ ๋ก๋ฉ ์ค์ผ ๋ <Loader />๋ฅผ ๋ฐํํ๊ณ ์๋ฌ๊ฐ ๋ฐ์ํ์๋ <Error />๋ฅผ ๋ฐํํ๋ฉฐ ์ฌ์ฉ์์๊ฒ ์ ํํ ์ ๋ณด๋ฅผ ์ ๋ฌํ ์ ์๋ค.
React-Query๋ ๋น๋๊ธฐ ์์ฒญ์ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ ์บ์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. React-Query API๋ค์ด ์ ๊ณตํ๋ ํ๋กํผํฐ๋ฅผ ํ์ฉํ๋ฉด ์ข ๋ ๊น๋ํ๊ฒ ๋น๋๊ธฐ ๋ก์ง์ ์ฒ๋ฆฌํ ์ ์๋ค.
// SampleContents.tsx
const SampleContents = () => {
const {
data: sampleDatas,
isLoading,
error,
} = useQuery({ queryKey, queryFn });
if (isLoading) {
return <Loader />;
}
if (error) {
return <Error error={error} />;
}
return (
<div>
{sampleDatas.map(data => (
<Content data={data} />
))}
</div>
);
};
export default SampleContents;
React Query์ API useQuery๊ฐ ๋ฐํํ๋ isLoading, error์ ๊ฐ์ ํ๋กํผํฐ๋ฅผ ํตํด useEffect๋ฅผ ์ ๊ฑฐํ๊ณ useState ์ค์ด๋ ๋ฑ ์ปดํฌ๋ํธ ๋ด๋ถ ๋ก์ง์ ์ข ๋ ๊ฐ์ํํด์ ์ฒซ ๋ฒ์งธ ์์์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐ์ดํฐ ์์ฒญ ์ํ์ ๋ฐ๋ผ ๋ฐํ๋๋ ์ปดํฌ๋ํธ ๋ ๋๋ง ์ฌ๋ถ๋ฅผ ์์ฑํ ์ ์๋ค.
์ ์ธ์ ๋ฐ์ดํฐ ํจ์นญ์ React์ ์ฃผ์ ๊ฐ๋ ์ธ โ์ ์ธํ ํ๋ก๊ทธ๋๋ฐโ ์ ๊ธฐ๋ฐํ๋ค. ์ด๋ ์ํ์ ๋ณํ์ ๋ฐ๋ผ UI๋ฅผ ์ง์ ์กฐ์ํ์ง ์๊ณ , ์ด๋ค ์ํ์ ๋ฐ๋ผ UI๊ฐ ๋ณด์ฌ์ ธ์ผ ํ๋์ง๋ง ์ง์คํ ์ ์๋ค.
์ด๋ฌํ ์ ๊ทผ๋ฒ์ ๋น๋๊ธฐ ์์
์ ๋ณต์ก์ฑ์ ํฌ๊ฒ ์ค์ด์ฃผ๋ฉฐ ์ฝ๋์ ๊ฐ๋
์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํค๋๋ฐ, Suspense์ ErrorBoundary๋ ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๋น๋๊ธฐ ์์
๊ณผ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ค.
์ฐ์ Suspense์ Error Boundary์ ๋ํด ๊ฐ๋จํ๊ฒ ์์๋ณด์.
Suspense๋ฅผ ์ฌ์ฉํ๋ฉด ์์์ด ๋ก๋ฉ์ ์๋ฃํ ๋๊น์ง fallback์ ํ์ํ ์ ์๋ค. React๋ ์์์๊ฒ ํ์ํ ๋ชจ๋ ์ฝ๋์ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ๋ก๋ฉ fallback์ ํ์ํ๋ค.
// React ๊ณต์ ๋ฌธ์์ ์์
<Suspense fallback={<Loading />}>
<SampleContents />
</Suspense>
Error Boundary๋ ํ์ ์ปดํฌ๋ํธ ํธ๋ฆฌ์์ ๋ฐ์ํ๋ ์๋ฌ๋ฅผ ์บก์ฒํด์ ๋์ฒด UI๋ฅผ ๋ณด์ฌ์ฃผ๊ฑฐ๋ ์๋ฌ ๋ฆฌํฌํ ๋ฑ์ ์์ ์ ์ํํ๋ค.
ํ์์ ๋ฐ๋ผ ์ปค์คํฐ๋ง์ด์ง์ ํ ์ ์๋๋ฐ ์ด๋ ์๊ฐ์ด ๋๋ค๋ฉด ๋ค์ ํฌ์คํ ์ ์ ๋ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
ErrorBoundary๋ก Toast, ErrorFallback ๋ฑ ๊ณตํต์ ์ธ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํด๋ณด์
useQuery๋ฅผ Suspense, Error Boundary์ ํจ๊ป ์ฌ์ฉํ๋ ค๋ฉด suspense: true ์ต์ ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
// SampleWrapper.jsx
const SampleWrapper = () => {
// ...
return (
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loader />}>
<SampleContents />
</Suspense>
</ErrorBoundary>
);
};
export default SampleWrapper;
// SampleContents.jsx
const SampleContents = () => {
const { data: sampleDatas } = useQuery({ queryKey, queryFn, suspense: true });
return (
<div>
{sampleDatas.map(data => (
<Content data={data} />
))}
</div>
);
};
export default SampleContents;
Suspense๋ ์ปดํฌ๋ํธ๊ฐ ์ค๋น๋ ๋๊น์ง ๋๊ธฐํ๊ณ fallback props๋ก ๋ฐ์ ์ปดํฌ๋ํธ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ญํ ์ ํ๋ค. ์ด๋ก ์ธํด ๋ก๋ฉ ์ํ์ ๋ฐ๋ฅธ ๋ก์ง์ด ์ปดํฌ๋ํธ ์ธ๋ถ๋ก ์ถ์ถ๋์ด ๊ฐ ์ปดํฌ๋ํธ์์ ๋ก๋ฉ ์ํ๋ฅผ ํ์ธํ๊ณ ์ฒ๋ฆฌํ ํ์๊ฐ ์์ด์ง๋ค.
Error Boundary๋ ํ์ ์ปดํฌ๋ํธ ํธ๋ฆฌ์์ ๋ฐ์ํ๋ ์๋ฌ๋ฅผ ์บก์ณํ์ฌ ๋์ฒด UI๋ฅผ ๋ณด์ฌ์ฃผ๊ฑฐ๋ ์๋ฌ ๋ฆฌํฌํ ๋ฑ์ ์์ ์ ์ํํ๋ค. ์ด๋ก ์ธํด ๊ฐ๊ฐ์ ์ปดํฌ๋ํธ์์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ด ๋ถ์ฐ๋์ง ์๊ณ ํ ๊ณณ์์ ๊ด๋ฆฌ๋ ์ ์๋ค.
์์ ์ฝ๋์์ ๋ณผ ์ ์๋ฏ์ด, Suspense์ ErrorBoundary๋ฅผ ์ฌ์ฉํ๋ฉด ๋ก๋ฉ ์ํ์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ ์ปดํฌ๋ํธ ์ธ๋ถ๋ก ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ ์ ์๋๋ฐ, ์ปดํฌ๋ํธ๋ก๋ถํฐ ๋ถ๋ฆฌ๋ ๋ก์ง์ผ๋ก ์ฝ๋์ ๊ฐ๋ ์ฑ์ ๋์ด๊ณ ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ํ๋ค.
๐ก ํ
๋ชจ๋ ์ฟผ๋ฆฌ๋ฌธ์์ ์ฌ์ฉํ ๊ฒ์ด๋ผ๋ฉด QueryClient๋ฅผ ์์ฑํ ๋ ๊ธฐ๋ณธ ์ต์ ์ผ๋ก ์ง์ ํด ์ค ์ ์๋ค.const queryClient = new QueryClient({ defaultOptions: { queries: { suspense: true, }, }, });
๐ก ํ
ErrorBoundary๋ ํ์ ์ปดํฌ๋ํธ์ ์๋ฌ๋ค์ ๊ณตํต์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ค.const ChildComponent = () => { // ... return ( <Suspense fallback={<Loader />}> <SampleContents /> </Suspense> ); };const ParentComponent = () => { // ... // ํ์ ์ปดํฌ๋ํธ์์ ๋ฐ์ํ๋ error๋ฅผ ๋ชจ๋ ์บ์น return ( <ErrorBoundary fallback={<Error />}> <ChildComponent /> <ChildComponent2 /> <ChildComponent3 /> </ErrorBoundary> ); };
์ ์ธ์ ๋ฐ์ดํฐ ํจ์นญ์ ๋ง์ ์ฅ์ ์ ๊ฐ์ง๊ณ ์์ง๋ง, ๋ช ๊ฐ์ง ์ฃผ์์ฌํญ๋ ์กด์ฌํ๋ค.
๐ก ์ธ์์ ๊ณต์ง๋ ์๋ค
Suspense๋ก ๊ฐ์ผ ์ปดํฌ๋ํธ ๋ด์์ ์ฌ๋ฌ๊ฐ์ useQuery๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋น๋๊ธฐ ์์ฒญ Waterfall์ด ๋ฐ์ํ๋ค.
<Suspense fallback={<Loading />}>
<SampleContents /> // 3๊ฐ์ query๋ฌธ์ด ์๋ ์ปดํฌ๋ํธ
</Suspense>

<Suspense fallback={<Loading />}>
<SampleContents1 /> // 1๊ฐ์ query๋ฌธ์ด ์๋ ์ปดํฌ๋ํธ
<SampleContents2 /> // 1๊ฐ์ query๋ฌธ์ด ์๋ ์ปดํฌ๋ํธ
<SampleContents3 /> // 1๊ฐ์ query๋ฌธ์ด ์๋ ์ปดํฌ๋ํธ
</Suspense>
React Query v4.5 ์ดํ ์ต์ ๋ฒ์ ์ ์ฌ์ฉํ๋ค๋ฉด useQuery ๋์ useQueries์ ์ฌ์ฉ์ ๊ณ ๋ คํด ๋ณผ ๋งํ๋ค.

๋น๋๊ธฐ ์์ฒญ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํด ์ฃผ๋ฉฐ Suspense๋ฅผ ์ง์ํด ์ค๋ค!
๋ฐ์ดํฐ ํจ์นญ ์๊ฐ์ด ์งง์ ๊ฒฝ์ฐ Loader๋ Skeleton UI ๊ฐ์ ์ปดํฌ๋ํธ์ ๋ ธ์ถ์ ์ฌ์ฉ์์๊ฒ ๊น๋นก์ด๋ ์ค๋ฅ ํ์ ์ฒ๋ผ ๋ณด์ผ ์ ์์ด ์คํ๋ ค ๋ถ์ ํํ ์ ๋ณด ์ ๋ฌ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ ์ ํ๋ก ์ด์ด์ง ์ ์์ผ๋ฏ๋ก ์ ์คํ๊ฒ ๊ณ ๋ คํด์ผ ํ๋ค.
const DeferredLoader = () => {
const [isDeferred, setIsDeferred] = useState(false);
useEffect(() => {
const id = setTimeout(() => {
setIsDeferred(true);
}, 200);
return () => clearTimeout(id);
}, []);
if (!isDeferred) {
return null;
}
return <Loader />;
};
์งง์ ์๊ฐ ๋ด์ ๋น๋๊ธฐ ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ค๋ฉด Loader๋ Skeleton UI ๋์ null์ ๋ฐํํ์ฌ ํ๋ฉด์ ๊ทธ๋ฆฌ์ง ์๊ณ ๊ทธ ์ด์์ ๋ก๋ฉ ์ Loader๋ Skeleton UI๋ฅผ ๋ ธ์ถํ๋ ๋ฐฉ์๋ ๊ณ ๋ คํ ๋งํ๋ค.
์ง๊ธ๊น์ง Suspense์ ErrorBoundary๋ฅผ ํ์ฉํ ์ ์ธ์ ๋ฐ์ดํฐ ํจ์นญ์ ๋ํด ์์๋ดค๋ค.
Suspense์ ErrorBoundary๋ฅผ ํ์ฉํ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ ์ปดํฌ๋ํธ์ ๊ฐ๋ ์ฑ, ์ ์ง๋ณด์ ์ธก๋ฉด, ๋น์ฆ๋์ค ๋ก์ง ๋ถ๋ฆฌ ์ธก๋ฉด์์ ์๋นํ ์ด์ ์ด ์์ง๋ง, ๋ฌด๋ถ๋ณํ Suspense, ErrorBoundary๋ ์ฑ๋ฅ ์ ํ, ์ฌ์ฉ์ ๊ฒฝํ ์ ํ๋ฅผ ์ผ์ผํฌ ์ ์๊ธฐ์ ์ฅ๋จ์ ์ ์ข ๋ ๊ณ ๋ฏผํ๊ณ ๋์ ์๋ฆฌ์ ์ ์ ํ ์ฌ์ฉ์ฑ์ ๋ํด์ ๋ ผ์ํด์ผ ํ ๊ฒ ๊ฐ๋ค.