๐Ÿ—‚๏ธ Suspense Component + useSuspenseQuery ๋„์ž…๊ธฐ (2)

๋ฐ•์›๋นˆยท2024๋…„ 6์›” 25์ผ

๋ฆฌ์•กํŠธ โš™๏ธ

๋ชฉ๋ก ๋ณด๊ธฐ
11/14
post-thumbnail

์ €๋ฒˆ ๊ธ€์— ์ด์–ด ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ œ๊ฐ€ ์–ด๋–ป๊ฒŒ Suspense ์ปดํฌ๋„ŒํŠธ์™€
useSuspenseQuery ๋ฅผ ๋„์ž…ํ•˜์—ฌ Skeleton UI๋ฅผ ๊ตฌํ˜„ํ•˜์˜€๋Š”์ง€์— ๋Œ€ํ•ด ํฌ์ŠคํŒ…ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


Skeleton UI์˜ ํ•„์š”์„ฑ์„ ๋А๋‚€ ๋ถ€๋ถ„

๋จผ์ € ์ด๋ฒˆ ๊ฐœ์ธ ๊ณผ์ œ๋Š” https://restcountries.com ์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š”
API๋ฅผ ํ™œ์šฉํ•ด ๋‚˜๋ผ์˜ ์ •๋ณด๋“ค์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ณผ์ œ์˜€์Šต๋‹ˆ๋‹ค.
๋”ฐ๋กœ ํŽ˜์ด์ง€๋„ค์ด์…˜์— ๋Œ€ํ•œ ์•ˆ๋‚ด๋Š” ์—†์–ด, ๋ชจ๋“  ๋‚˜๋ผ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋Š”
https://restcountries.com/v3.1/all API๋ฅผ ํ˜ธ์ถœํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•  ๋•Œ๋งˆ๋‹ค ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฐ€์ ธ์˜ฌ ๋‚˜๋ผ์˜ ๋ฐ์ดํ„ฐ๋Š” 250๊ฐœ์˜€๊ณ , ์ด ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ์— ๋ถˆ๋Ÿฌ์™€์„œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐœ์ƒ์‹œ์ผœ๋ณด๋‹ˆ
์œ„์™€ ๊ฐ™์ด ๋‚˜๋ผ์˜ ์ •๋ณด๊ฐ€ ์–ด์ƒ‰ํ•˜๊ฒŒ ์ง ! ํ•˜๊ณ  ๋‚˜ํƒ€๋‚˜๋Š” ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ์ €์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ถˆ๊ณผ 1์ดˆ ํ˜น์€ 1.5์ดˆ๋งŒ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถˆ๋ ค์™€์ง€์ง€๋งŒ
์ข‹์ง€ ์•Š์€ ํ™˜๊ฒฝ์—์„œ ์›น์‚ฌ์ดํŠธ์— ์ ‘์†ํ•œ๋‹ค๋ฉด ์ตœ์•…์˜ ์ƒํ™ฉ์—์„œ๋Š” 5์ดˆ ํ˜น์€ ๊ทธ ์ด์ƒ์˜ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋„
์•„๋ฌด๊ฒƒ๋„ ๋ณด์ด์ง€ ์•Š๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ณ ,
๊ทธ ์œ ์ €๋Š” ์›น์‚ฌ์ดํŠธ๊ฐ€ ์ž‘๋™์„ ํ•˜์ง€ ์•Š๋Š”๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „ ํ™”๋ฉด์„ ๋‹ซ์•„๋ฒ„๋ฆด์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์กฐ๊ธˆ ๋” ์ž์—ฐ์Šค๋Ÿฌ์šด UX๊ฐ€ ๋ญ๊ฐ€ ์žˆ์„๊นŒ.. ํ•˜๋ฉฐ ์ƒ๊ฐํ•˜๋˜ ๋„์ค‘ Skeleton UI๊ฐ€ ๋– ์˜ฌ๋ผ
๊ตฌํ˜„์„ ๊ฒฐ์ •ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์–‘ํ•œ ๊ตฌํ˜„ ๋ฐฉ์‹์—์„œ์˜ ๊ณ ๋ฏผ

1. useQuery ํ›…์—์„œ ์ œ๊ณตํ•˜๋Š” isFetching ๊ณผ ๊ฐ™์€ ์ƒํƒœ๋กœ ์‚ฌ์šฉํ•œ๋‹ค

๋‹จ์ˆœํ•˜๊ฒŒ Skeleton UI๋ฅผ ์ ์šฉ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์ค‘์—๋Š” ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ๋ Œ๋”๋ง์„ ํ•ด์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์„ ํ†ตํ•ด
isFetching ์ƒํƒœ๊ฐ€ true ๋ผ๋ฉด Skeleton UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ ,
isFetching ์ƒํƒœ๊ฐ€ false ๋ผ๋ฉด ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

2. React์—์„œ ์ œ๊ณตํ•˜๋Š” Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค

React Docs๋ฅผ ํƒ๋ฐฉํ•˜๋˜ ๋„์ค‘, <Suspense> ๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
Suspense ์ปดํฌ๋„ŒํŠธ๋Š” ์ž์‹ ์š”์†Œ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ™”๋ฉด์— ๋Œ€์ฒด UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ๋กœ
์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ๋„ ๊ตฌํ˜„์„ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ๋ฐฉ์‹์ด ๋” ์ข‹์„๊นŒ?

์–ด๋–ค ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„ํ• ์ง€์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•ด๋ณด์•˜๋Š”๋ฐ, ๊ณ ๋ฏผ์˜ ์ „์ œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๊ณ  ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผ ํ”„๋กœ์ ํŠธ์˜ ์‚ฌ์ด์ฆˆ๊ฐ€ ์ปค์ ธ๋„ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ €ํ•˜๋˜์ง€ ์•Š์„๊นŒ?

  1. useQuery ํ›…์—์„œ ์ œ๊ณตํ•˜๋Š” isFetching ๊ณผ ๊ฐ™์€ ์ƒํƒœ๋กœ ์‚ฌ์šฉํ•œ๋‹ค
  • ์žฅ์  : ๋‹จ์ˆœํžˆ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง๋งŒ์„ ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด ๊ตฌํ˜„์˜ ๋‚œ์ด๋„๊ฐ€ ๋ณต์žกํ•˜์ง€ ์•Š๋‹ค.
  • ๋‹จ์  : ๋งŒ์•ฝ ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๊ณ  ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ๋˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ์„ ๊ฒฝ์šฐ, ๋ณต์žกํ•˜๊ฒŒ ๊ตฌํ˜„๋œ ๊ณณ๊นŒ์ง€ ๋“ค์–ด๊ฐ€ ๋กœ์ง์„ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.
  1. React์—์„œ ์ œ๊ณตํ•˜๋Š” Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
  • ์žฅ์  : ์ปดํฌ๋„ŒํŠธ ์ž์ฒด์—์„œ Promise ๋ฅผ catchํ•ด fallback ๋กœ๋”๋ฅผ ํ‘œ์‹œํ•ด์ฃผ๊ณ ,
    ๋ณต์žกํ•˜๊ฒŒ ๊ตฌํ˜„๋œ ๊ณณ๊นŒ์ง€ ๋“ค์–ด๊ฐ€ ๋กœ์ง์„ ํ™•์ธํ•˜๊ณ  ์ผ์ผํžˆ ์ˆ˜์ •ํ•ด์•ผ๋˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์‚ฌ๋ผ์ง„๋‹ค.
  • ๋‹จ์  : Suspense ์ปดํฌ๋„ŒํŠธ๋Š” Suspense๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™œ์„ฑํ™” ์‹œ์ผœ์ค€๋‹ค.
    useEffect ๋‚˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋Š” ๊ฐ์ง€ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์œ„์˜ ์žฅ์ ๊ณผ ๋‹จ์ ์„ ๋น„๊ตํ•ด๋ณด๋ฉฐ ์ƒ๊ฐํ•ด๋ณด์•˜์„๋•Œ, 2๋ฒˆ์˜ ๋ฐฉ์‹์ด ์ œ๊ฐ€ ์ƒ๊ฐํ•˜๋Š”
๊ตฌํ˜„ ๋ฐฉ์‹์— ๋” ์ ํ•ฉํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•ด Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๊ณผ์ •

1. ๋จผ์ € ์ ์šฉ์‹œํ‚ค๊ณ ์ž ํ•˜๋Š”๊ณณ์— Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ์ค€๋‹ค.

// App.tsx
<Suspense>
  <CountryList />
</Suspense>

2. fallback Props์— ๋“ค์–ด๊ฐˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ๋„ฃ์–ด์ค€๋‹ค.

// CountryItemSkeleton.tsx
export const CountryItemSkeleton = () => {
  return (
    <div className="flex h-[260px] w-[292px] animate-pulse flex-col items-center rounded bg-gray-300 p-4 shadow-md" />
  );
};

๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์— ๋ณด์—ฌ์ค„, ์ฆ‰ Skeleton UI๋กœ ์‚ฌ์šฉํ•ด์ค„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ์ž‘ํ•ด์ค๋‹ˆ๋‹ค.

// App.tsx
<Suspense
  fallback={Array.from({ length: 20 }).map((_, idx) => (
    <CountryItemSkeleton key={idx} />
  ))}
  >
  <CountryList />
</Suspense>

๊ทธ๋ฆฌ๊ณ  ๊ฐ์‹ธ์ฃผ์—ˆ๋˜ Suspense ์ปดํฌ๋„ŒํŠธ์˜ props๋กœ ์Šค์ผˆ๋ ˆํ†ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

์œ„ ๊ณผ์ •์—์„œ์˜ ๊ณ ๋ฏผ - ์Šค์ผˆ๋ ˆํ†ค์„ ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งž๋Š”์ง€

๋‚˜๋ผ์˜ ๋ฐ์ดํ„ฐ๋Š” ์ด 250๊ฐœ๊ฐ€ ์˜ค๋Š”๋ฐ ์Šค์ผˆ๋ ˆํ†ค์œผ๋กœ ๋ณด์—ฌ์ค˜์•ผํ•˜๋Š” ๋ฐ์ดํ„ฐ์˜ ์–‘์„ ์–ผ๋งˆ์ •๋„๋กœ ํ•ด์•ผ ์ ๋‹นํ•œ๊ฑด์ง€,
Suspense ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•ด์„œ ๋ณด์—ฌ์ฃผ๋Š”๊ฒƒ์ด ๋งž๋Š”๊ฑด์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์ด ๋ถ€๋ถ„์€ ์งˆ๋ฌธ์„ ํ†ตํ•ด ๋‹ต์„ ์–ป์–ด์•ผํ•˜๋Š” ๋ถ€๋ถ„์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

3. ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” CountryList ์ปดํฌ๋„ŒํŠธ์— ๋กœ์ง์„ ์ž‘์„ฑํ•ด์ค€๋‹ค.

// CountryList.tsx
const CountryList = () => {
  const { data: countryList } = useSuspenseQuery({
    queryKey: ["countryList"],
    queryFn: () => api.country.getAllCountry(),
  });

  return (
    <ul className="flex flex-wrap justify-center gap-4">
      {countryList.map((country) => (
        <CountryItem
          key={country.name.common}
          country={country}
        />
      ))}
    </ul>
  );
};

export default CountryList;

์œ„ ๋กœ์ง์„ ์ž‘์„ฑํ•˜๋ฉฐ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ

๊ธฐ์กด์—๋Š” useQuery ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง์„ ๋งŒ๋“ค์—ˆ์ง€๋งŒ,
์–ด์งธ์„œ์ธ์ง€ Suspense ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž‘๋™์„ ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
๋ฌธ์ œ๋ฅผ ๋งˆ์ฃผํ•˜๊ณ  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๋˜ ๋„์ค‘, React ๊ณต์‹๋ฌธ์„œ์˜ Suspense ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ๋ฒ•์„ ๋ณด์•˜๋Š”๋ฐ
์œ„์—์„œ ์ ์–ด๋†“์•˜๋“ฏ์ด Suspense ์ปดํฌ๋„ŒํŠธ๋Š” Suspense ๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ์—๋งŒ ํ™œ์„ฑํ™”๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋˜ ํ›…์ธ useQuery ๋Œ€์‹  useSuspenseQuery ํ›…์„ ์‚ฌ์šฉํ•ด์•ผ
์ •์ƒ์ ์œผ๋กœ ์ž‘๋™์ด ๋˜๋Š”๊ฒƒ์„ ํŒŒ์•…ํ•˜๊ณ , ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
์ด ๊ณผ์ •์—์„œ๋„ ์—ญ์‹œ Tanstack Query ๊ณต์‹๋ฌธ์„œ๋ฅผ ๋ณด๋ฉฐ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Before / Affer

Before

Affer


ํ›จ์”ฌ ์ž์—ฐ์Šค๋Ÿฝ์ง€ ์•Š๋‚˜์š”?
์ €๋Š” ์ด ๊ธฐ์ˆ ์„ ๋„์ž…ํ•˜๋ฉด์„œ ๊ฐœ์ธ์ ์œผ๋กœ๋„ ๊ต‰์žฅํžˆ ๋ฟŒ๋“ฏํ–ˆ์Šต๋‹ˆ๋‹ค.
์•ž์œผ๋กœ๋„ ๊ฐœ์ธํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์œ ์ €์˜ ์ž…์žฅ์—์„œ๋Š” ๋ฌด์—‡์ด ๋ถˆํŽธํ• ๊นŒ,
๊ทธ๊ฒƒ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด์•ผํ• ๊นŒ์— ๋Œ€ํ•ด์„œ
๊ณ ๋ฏผ์„ ๋งŽ์ด ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•ด์•ผ๊ฒ ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€