07. ๐Ÿ“ก ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ์บ์‹ฑ ์ „๋žต ๋งˆ์Šคํ„ฐ

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

React Query(TanStack Query)๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ '์ƒํƒœ'๊ฐ€ ์•„๋‹Œ '์บ์‹œ'๋กœ ๋‹ค๋ฃจ๋Š” ํŒจ๋Ÿฌ๋‹ค์ž„ ๋ณ€ํ™”๋ฅผ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ์ด ๋ฉด์ ‘ ํ•ญ๋ชฉ์˜ ๋ชฉํ‘œ

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 24๋ถ„ (ํ•ต์‹ฌ ์š”์•ฝ: 12๋ถ„)

๐Ÿ—บ๏ธ ์ด ์ฑ•ํ„ฐ์˜ ํ๋ฆ„
[๊ฐœ๋… ์‚ฌ์ „] โ†’ [์งˆ๋ฌธ 1: ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ vs ์„œ๋ฒ„ ์ƒํƒœ] โ†’ [์งˆ๋ฌธ 2: ์บ์‹ฑ ๋ฐ ์‹ ์„ ๋„ ๊ด€๋ฆฌ] โ†’ [์‹ค์ „ ๋ณ€ํ˜• ์งˆ๋ฌธ]

๐ŸŽฏ ์ด ์ฑ•ํ„ฐ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ์™œ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์—ญ ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Zustand ๋“ฑ)์— ์ €์žฅํ•˜๋ฉด ์•ˆ ๋˜๋Š”์ง€ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • Stale-While-Revalidate ์ „๋žต์ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”์ง€ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฌดํ•œ ์Šคํฌ๋กค, ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ๋“ฑ ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ํŒจํ„ด์˜ ๊ตฌํ˜„ ์›๋ฆฌ๋ฅผ ๋งˆ์Šคํ„ฐํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“š ํ•ต์‹ฌ ๊ฐœ๋… ์‚ฌ์ „ (Concept Glossary)

1. ์„œ๋ฒ„ ์ƒํƒœ (Server State)

๋‚ด๊ฐ€ ์†Œ์œ ํ•˜์ง€ ์•Š์€, ์›๊ฒฉ์ง€์˜ DB์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์–ธ์ œ๋“  ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์— ์˜ํ•ด ๋ฐ”๋€” ์ˆ˜ ์žˆ์œผ๋ฉฐ(Out-of-sync), ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” ๋น„๋™๊ธฐ์  ํŠน์„ฑ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

2. SWR (Stale-While-Revalidate)

"์ƒํ•œ(Stale) ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ๋ณด์—ฌ์ฃผ๋˜, ๊ทธ๋™์•ˆ ์„œ๋ฒ„์—์„œ ์‹ ์„ ํ•œ(Fresh) ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€(Revalidate) ์กฐ์šฉํžˆ ๊ต์ฒดํ•œ๋‹ค"๋Š” ์บ์‹ฑ ์ „๋žต์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ '๋Œ€๊ธฐ ์‹œ๊ฐ„ 0'์˜ ๊ฒฝํ—˜์„ ์ค๋‹ˆ๋‹ค.

3. ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ (Optimistic Update)

์„œ๋ฒ„์˜ ์‘๋‹ต์ด ์˜ค๊ธฐ๋„ ์ „์— ๋ฏธ๋ฆฌ UI๋ฅผ '์„ฑ๊ณต'ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ฐ”๊พธ๋Š” ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ข‹์•„์š” ๋ฒ„ํŠผ์ด๋‚˜ ๋Œ“๊ธ€ ์ž‘์„ฑ์ฒ˜๋Ÿผ ๋น ๋ฅด๊ธฐ ์ฐจ์ด๊ฐ€ UX์— ํฐ ์˜ํ–ฅ์„ ์ฃผ๋Š” ๊ธฐ๋Šฅ์— ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.


๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ๐Ÿฃ ์˜์ฒ  (ํ›„๋ฐ˜ ์ดˆ์ž…): "์˜ํ˜ธ ๋‹˜! '์˜์ˆ˜๋„ค ๊ฒŒ์‹œํŒ' ๋ฐ์ดํ„ฐ๋ฅผ Redux์— ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋А๋ผ loading, error, success ์ƒํƒœ ๋ณ€์ˆ˜๊ฐ€ ๊ณ„์† ๋Š˜์–ด๋‚ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์™€ UI ์ƒํƒœ๋ฅผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋‹ค๋ค„๋„ ๋˜๋Š”์ง€ ํ—ท๊ฐˆ๋ ค์š”."
  • ๐Ÿฆ ์˜ํ˜ธ (๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜, ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์†Œ์œ ํ•œ ์ƒํƒœ๋ผ๊ธฐ๋ณด๋‹ค ์„œ๋ฒ„์˜ ์›๊ฒฉ ์Šค๋ƒ…์ƒท์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ์•ฑ ์ƒํƒœ์— ๋ฌด๋ฆฌํ•˜๊ฒŒ ๋งž์ถ”๊ธฐ๋ณด๋‹ค ์บ์‹œ, ์‹ ์„ ๋„, ์žฌ๊ฒ€์ฆ์„ ๋‹ค๋ฃจ๋Š” ๋„๊ตฌ์— ๋งก๊ธฐ๊ณ  ์ปดํฌ๋„ŒํŠธ๋Š” ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌ๋…ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค."

๋ฉด์ ‘ ์งˆ๋ฌธ 1. ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(Client State)์™€ ์„œ๋ฒ„ ์ƒํƒœ(Server State)๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

๐ŸŽฏ ์ถœ์ œ ์˜๋„

๋ฐ์ดํ„ฐ์˜ ์†Œ์œ ๊ถŒ๊ณผ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ์™€ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜(Data Inconsistency) ๋ฒ„๊ทธ์— ์‹œ๋‹ฌ๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ Naive ๊ตฌํ˜„ (Bad Case)

์˜์ฒ ์ด๋Š” ๋ชจ๋“  API ์‘๋‹ต์„ ํด๋ผ์ด์–ธํŠธ ์ „์—ญ ์Šคํ† ์–ด์— ์ง์ ‘ ๋„ฃ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

// ๐Ÿฃ ์˜์ฒ : "์œ ์ € ๋ชฉ๋ก๋„ ์ „์—ญ ์ƒํƒœ๋‹ˆ๊นŒ Redux์— ์ €์žฅํ•ด์•ผ์ง€!"
function Users() {
  const dispatch = useDispatch();
  const { data, isLoading } = useSelector(state => state.users);
 
  useEffect(() => {
    dispatch(fetchUsers()); // โš ๏ธ ๋กœ๋”ฉ, ์—๋Ÿฌ, ์บ์‹œ ๋ฌดํšจํ™” ๋กœ์ง์„ ์ง์ ‘ ๋‹ค ๊ตฌํ˜„ํ•ด์•ผ ํ•จ
  }, []);
 
  // โš ๏ธ ๋ฌธ์ œ: ๋‹ค๋ฅธ ํƒญ์—์„œ ์œ ์ €๊ฐ€ ์ถ”๊ฐ€๋˜์–ด๋„ ์ด ์•ฑ์€ ์•Œ ๋ฐฉ๋ฒ•์ด ์—†์Œ (Outdated)
}

๐Ÿฆ ์˜ํ˜ธ์˜ ๋ฆฌ๋ทฐ ํฌ์ธํŠธ
"์˜์ฒ  ๋‹˜, ์œ ์ € ๋ชฉ๋ก์€ ์šฐ๋ฆฌ ์•ฑ์ด ์ฃผ์ธ์ด ์•„๋‹ˆ์—์š”. ์„œ๋ฒ„๊ฐ€ ์ฃผ์ธ์ด์ฃ . ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์„œ๋ฒ„๊ฐ€ ์•Œ๋ ค์ฃผ๊ฑฐ๋‚˜ ์šฐ๋ฆฌ๊ฐ€ ๋‹ค์‹œ ๋ฌผ์–ด๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(๋‹คํฌ๋ชจ๋“œ ์—ฌ๋ถ€ ๋“ฑ)์™€๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅธ ์ƒํƒœ๊ณ„์˜ˆ์š”."

๐Ÿฆ ์˜ํ˜ธ์˜ ์•„ํ‚คํ…์ฒ˜ ๊ฐ€์ด๋“œ (Good Case)

์˜ํ˜ธ ๋ฆฌ๋“œ๋Š” React Query๋ฅผ ์‚ฌ์šฉํ•ด ์„ ์–ธ์ ์œผ๋กœ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

// ๐Ÿฆ ์˜ํ˜ธ: "๋ฐ์ดํ„ฐ ํŽ˜์นญ์€ '๋ช…๋ น'์ด ์•„๋‹ˆ๋ผ '์„ ์–ธ'์ž…๋‹ˆ๋‹ค."
 
function Users() {
  // โœ… ๋กœ๋”ฉ, ์—๋Ÿฌ, ๋ฐ์ดํ„ฐ, ์‹ ์„ ๋„ ์ฒดํฌ๊นŒ์ง€ ํ•œ ์ค„๋กœ ํ•ด๊ฒฐ
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    staleTime: 1000 * 60 * 5, // 5๋ถ„ ๋™์•ˆ์€ ์‹ ์„ ํ•œ ๋ฐ์ดํ„ฐ๋กœ ๊ฐ„์ฃผ (์บ์‹œ ํ™œ์šฉ)
  });
 
  if (isLoading) return <Spinner />;
  return <div>{data.map(user => <div key={user.id}>{user.name}</div>)}</div>;
}

์ด ์ฝ”๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ "๊ฐ€์ ธ์˜ค๋ผ"๊ณ  ๋ช…๋ นํ•˜๊ธฐ๋ณด๋‹ค ['users']๋ผ๋Š” ํ‚ค์˜ ์„œ๋ฒ„ ์Šค๋ƒ…์ƒท์„ ๊ตฌ๋…ํ•œ๋‹ค๊ณ  ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ ํ‚ค๋ฅผ ์“ฐ๋Š” ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋Š” ์บ์‹œ์™€ ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ์„ ๊ณต์œ ํ•˜๋ฏ€๋กœ ๋กœ๋”ฉ, ์—๋Ÿฌ, ์ค‘๋ณต ์š”์ฒญ ์ฒ˜๋ฆฌ๋ฅผ ๋งค๋ฒˆ ๋‹ค์‹œ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

๐Ÿ“Š ๋ ˆ๋ฒจ๋ณ„ ๋‹ต๋ณ€ ๊ฐ€์ด๋“œ (Self-Check)

  • Level 1 (Junior): "์„œ๋ฒ„ ์ƒํƒœ๋Š” API๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ์ดํ„ฐ๊ณ , ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋Š” UI์šฉ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋Š” ๋กœ๋”ฉ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
  • Level 2 (Senior): "์†Œ์œ ๊ถŒ(Ownership)์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ƒํƒœ๋Š” ๋น„๋™๊ธฐ์ ์ด๊ณ  ๋™๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณต์œ ํ•œ๋‹ค๋Š” ์ ์„ ์ง€์ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์บ์‹ฑ, ์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฐ์ดํŠธ ๋“ฑ์ด ํ•„์š”ํ•จ์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค."
  • Level 3 (Specialist): "์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์–ด๋–ป๊ฒŒ 'SSOT(Single Source of Truth)' ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š”์ง€ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ฆฌ์•กํŠธ์˜ '์„ ์–ธ์  UI' ํŒจ๋Ÿฌ๋‹ค์ž„๊ณผ ์–ด๋–ป๊ฒŒ ์ผ์น˜ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์ด๋กœ ์ธํ•ด ์ „์—ญ ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์—ญํ• ์ด '์ˆœ์ˆ˜ UI ์ƒํƒœ'๋กœ๋งŒ ์ถ•์†Œ๋˜๋Š” ์•„ํ‚คํ…์ฒ˜์  ์šฐ์•„ํ•จ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค."

๋ฉด์ ‘ ์งˆ๋ฌธ 2. staleTime๊ณผ gcTime์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•˜๊ณ , ์ด๋ฅผ ํ™œ์šฉํ•œ ์ตœ์ ์˜ ์บ์‹œ ์ „๋žต์„ ์ œ์‹œํ•ด ๋ณด์„ธ์š”.

๐ŸŽฏ ์ถœ์ œ ์˜๋„

React Query์˜ ํ•ต์‹ฌ ๋งค์ปค๋‹ˆ์ฆ˜์ธ '์‹ ์„ ๋„'์™€ '๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ'๋ฅผ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ํŠœ๋‹ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ Naive ๊ตฌํ˜„ (Bad Case)

์˜์ฒ ์ด๋Š” ๋ชจ๋“  API ์š”์ฒญ์— ๋Œ€ํ•ด ๊ธฐ๋ณธ ์„ค์ •๋งŒ ์“ฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

// ๐Ÿฃ ์˜์ฒ : "์„ค์ •๊ฐ’์ด ๋„ˆ๋ฌด ๋งŽ์•„์„œ ๊ทธ๋ƒฅ ๊ธฐ๋ณธ์œผ๋กœ ์จ์š”. ์•Œ์•„์„œ ์ž˜ ๋˜๊ฒ ์ฃ ?"
const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
// โš ๏ธ ๋ฌธ์ œ: ํŽ˜์ด์ง€๋ฅผ ์ž ๊น ๋‚˜๊ฐ”๋‹ค ์˜ฌ ๋•Œ๋งˆ๋‹ค ๋งค๋ฒˆ API ์žฌํ˜ธ์ถœ ๋ฐœ์ƒ (์„œ๋ฒ„ ๋ถ€ํ•˜)

๐Ÿฆ ์˜ํ˜ธ์˜ ๋ฆฌ๋ทฐ ํฌ์ธํŠธ
"์˜์ฒ  ๋‹˜, ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์€ ๋ณดํ†ต 1์ดˆ๋งˆ๋‹ค ๋ฐ”๋€Œ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋งค๋ฒˆ ์„œ๋ฒ„์— ๋‹ค์‹œ ๋ฌผ์œผ๋ฉด ์‚ฌ์šฉ์ž๋„ ์„œ๋ฒ„๋„ ๋ถˆํ•„์š”ํ•œ ๋น„์šฉ์„ ์น˜๋Ÿฌ์š”. ๋ฐ์ดํ„ฐ๋ฅผ ์–ผ๋งˆ๋‚˜ '์‹ ์„ (Fresh)'ํ•˜๋‹ค๊ณ  ๋ฏฟ์–ด์ค„์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒŒ ์‹œ๋‹ˆ์–ด์˜ ์„ธ์‹ฌํ•จ์ž…๋‹ˆ๋‹ค."

๐Ÿฆ ์˜ํ˜ธ์˜ ์•„ํ‚คํ…์ฒ˜ ๊ฐ€์ด๋“œ (Good Case)

์˜ํ˜ธ ๋ฆฌ๋“œ๊ฐ€ ์ƒํ™ฉ์— ๋”ฐ๋ฅธ ์บ์‹œ ์„ค์ •๋ฒ•์„ ์ „์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿฆ ์˜ํ˜ธ: "๋ฐ์ดํ„ฐ์˜ ์„ฑ๊ฒฉ์— ๋”ฐ๋ผ ์œ ํ†ต๊ธฐํ•œ์„ ์ •ํ•˜์„ธ์š”."
 
// 1. ๊ณต์ง€์‚ฌํ•ญ: ์ž˜ ์•ˆ ๋ณ€ํ•จ -> staleTime์„ ๊ธธ๊ฒŒ (์˜ˆ: 1์‹œ๊ฐ„)
const noticeQuery = useQuery({
  queryKey: ['notices'],
  queryFn: fetchNotices,
  staleTime: 1000 * 60 * 60,
});
 
// 2. ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…: ๊ณ„์† ๋ณ€ํ•จ -> staleTime์€ 0 (๋งˆ์šดํŠธ/ํฌ์ปค์Šค ๋“ฑ์—์„œ ์žฌ๊ฒ€์ฆ)
const chatQuery = useQuery({
  queryKey: ['chats'],
  queryFn: fetchChats,
  staleTime: 0,
});
 
// ๐Ÿ’ก ํŒ: gcTime์€ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฟผ๋ฆฌ ์บ์‹œ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์•„์žˆ๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค.
// ๋ณดํ†ต staleTime๋ณด๋‹ค ๊ธธ๊ฒŒ ๋‘์–ด, ์ž ๊น ์ด๋™ํ–ˆ๋‹ค ๋Œ์•„์™”์„ ๋•Œ ์บ์‹œ๋ฅผ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

TanStack Query v5์—์„œ๋Š” ์˜ˆ์ „ cacheTime ์ด๋ฆ„์ด gcTime์œผ๋กœ ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค. staleTime์€ "๋‹ค์‹œ ๋ฌผ์–ด๋ณผ ํ•„์š”๊ฐ€ ์žˆ๋Š”๊ฐ€"๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ , gcTime์€ "์•„๋ฌด๋„ ๊ตฌ๋…ํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์–ธ์ œ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์น˜์šธ ๊ฒƒ์ธ๊ฐ€"๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Š ๋ ˆ๋ฒจ๋ณ„ ๋‹ต๋ณ€ ๊ฐ€์ด๋“œ (Self-Check)

  • Level 1 (Junior): "staleTime์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ ์„ ํ•œ ์‹œ๊ฐ„์ด๊ณ , gcTime์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์บ์‹œ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์•„์žˆ๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค."
  • Level 2 (Senior): "๋ฐ์ดํ„ฐ๊ฐ€ 'stale'ํ•ด์ ธ๋„ ์ฆ‰์‹œ ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žฌ๊ฒ€์ฆ(Revalidation)์˜ ๋Œ€์ƒ์ด ๋จ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. gcTime์€ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ์ž‘๋™ํ•˜๊ธฐ ์ „๊นŒ์ง€์˜ ์—ฌ์œ  ์‹œ๊ฐ„์ž„์„ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค."
  • Level 3 (Specialist): "์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ด€์ ์—์„œ staleTime์„ ๊ธธ๊ฒŒ ๋‘๊ณ  mutation ์ดํ›„ invalidateQueries๋ฅผ ์ˆ˜๋™์œผ๋กœ ํ˜ธ์ถœํ•˜๋Š” '์ •์  ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ' ์ „๋žต์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๋˜๋Š” prefetchQuery์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ๋‹ค์Œ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ์บ์‹œ์— ๋„ฃ์–ด ์ „ํ™˜ ์ง€์—ฐ์„ ์ค„์ด๋Š” ๊ธฐ๋ฒ•์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค."

๋ฉด์ ‘ ์งˆ๋ฌธ 72. ์ฟผ๋ฆฌ ๋ฌดํšจํ™”(Invalidation)์™€ ์ˆ˜๋™ ์—…๋ฐ์ดํŠธ(SetQueryData)์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • ๐ŸŽฏ ์ถœ์ œ ์˜๋„: ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์ง€ํ‚ค๋Š” ๋‘ ๊ฐ€์ง€ ์ „๋žต์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ ์ดํ•ดํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ’ก ํ•ต์‹ฌ ์›๋ฆฌ & ๋‹ต๋ณ€: Invalidate๋Š” "์ด ๋ฐ์ดํ„ฐ๋Š” ์ƒํ–ˆ์œผ๋‹ˆ ์„œ๋ฒ„์—์„œ ๋‹ค์‹œ ๋ฐ›์•„์™€!"๋ผ๊ณ  ์„ ์–ธํ•˜๋Š” ์•ˆ์ „ํ•œ ๋ฐฉ์‹์ด๋ฉฐ, ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด SetQueryData๋Š” ์„œ๋ฒ„ ์‘๋‹ต ์ „์— ์บ์‹œ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” '๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ'์— ์ฃผ๋กœ ์“ฐ์ด๋ฉฐ, ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ํ•œ ๋ฒˆ ์•„๋‚„ ์ˆ˜ ์žˆ์–ด ํ›จ์”ฌ ๋น ๋ฆ…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์‹ฌ์Šค๋Ÿฝ๊ฒŒ ์กฐ์ž‘ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋ณต์žก๋„๊ฐ€ ๋†’๊ณ  ์‹คํŒจ ์‹œ ๋กค๋ฐฑ ๋กœ์ง์ด ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.

๋ฉด์ ‘ ์งˆ๋ฌธ 85. ๋ฌดํ•œ ์Šคํฌ๋กค(Infinite Scroll) ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ•  ํ•ต์‹ฌ ์„ฑ๋Šฅ ํฌ์ธํŠธ๋Š”?

  • ๐ŸŽฏ ์ถœ์ œ ์˜๋„: ๋ฐ์ดํ„ฐ ์–‘์ด ๋Š˜์–ด๋‚  ๋•Œ ์ƒ๊ธฐ๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ํ•œ๊ณ„์™€ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์กฐํ™”์‹œํ‚ค๋Š” ๋Šฅ๋ ฅ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ’ก ํ•ต์‹ฌ ์›๋ฆฌ & ๋‹ต๋ณ€: ์ฒซ์งธ, useInfiniteQuery๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ pageParam ๋‹จ์œ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์ด์ „/๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๋งค๋„๋Ÿฝ๊ฒŒ ํŽ˜์นญํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‘˜์งธ, ๋ Œ๋”๋ง ์ตœ์ ํ™”์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด DOM ๋…ธ๋“œ๊ฐ€ ๋ฌด๊ฑฐ์›Œ์ง€๋ฏ€๋กœ Intersection Observer๋Š” ๋ฌผ๋ก , ํ™”๋ฉด ๋ฐ–์˜ ๋…ธ๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” '์œˆ๋„์ž‰(Windowing/Virtualization)' ๊ธฐ๋ฒ•์„ ๋ณ‘ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋””๋ฐ”์šด์‹ฑ์ด๋‚˜ ์“ฐ๋กœํ‹€๋ง์„ ํ†ตํ•ด ๋ถˆํ•„์š”ํ•œ API ์š”์ฒญ์ด ์—ฐ์†์œผ๋กœ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฉด์ ‘ ์งˆ๋ฌธ 91. ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ™์€ Query๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ํ•œ ๋ฒˆ๋งŒ ๋ฐœ์ƒํ•˜๋Š” ์›๋ฆฌ๋Š”?

  • ๐ŸŽฏ ์ถœ์ œ ์˜๋„: ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋‚ด๋ถ€ ์บ์‹ฑ ๋ ˆ์ด์–ด์™€ ์š”์ฒญ ๋ณ‘ํ•ฉ(Request Deduplication) ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ดํ•ดํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ’ก ํ•ต์‹ฌ ์›๋ฆฌ & ๋‹ต๋ณ€: React Query๋Š” QueryClient๋ผ๋Š” ์ „์—ญ ์ €์žฅ์†Œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ๋ชจ๋“  ์ฟผ๋ฆฌ๋Š” ๊ณ ์œ ํ•œ QueryKey๋ฅผ ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ํ‚ค๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํ˜„์žฌ '์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ(Pending Promise)'์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ๋ณ„๋„์˜ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ ์—†์ด ํ•ด๋‹น ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ ์™„๋ฃŒ๋œ ๋ฐ์ดํ„ฐ๋ผ๋ฉด ์บ์‹œ๋œ ๊ฐ’์„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ staleTime์— ๋”ฐ๋ผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฐ์ดํŠธ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŒŒํŽธํ™”๋˜์–ด ์žˆ์–ด๋„ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ Zustand ๊ฐ™์€ ํด๋ผ์ด์–ธํŠธ ์ „์—ญ ์ƒํƒœ์— ๊ทธ๋Œ€๋กœ ๋„ฃ์œผ๋ฉด ์™œ ๋ฌธ์ œ๊ฐ€ ๋˜๊ธฐ ์‰ฌ์šด๊ฐ€์š”?

โœ… ์ •๋‹ต: ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์˜ ์†Œ์œ ๊ถŒ, ์‹ ์„ ๋„, ์žฌ๊ฒ€์ฆ, ์ค‘๋ณต ์š”์ฒญ, ์—๋Ÿฌ/๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: ์„œ๋ฒ„ ์ƒํƒœ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์›๋ณธ์„ ์†Œ์œ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ staleTime, refetch, invalidation, retry, background update ๊ฐ™์€ ์บ์‹œ ์ •์ฑ…์œผ๋กœ ๋‹ค๋ฃจ๋Š” ํŽธ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์ „์—ญ์—์„œ ์“ฐ๋‹ˆ๊นŒ ์ „์—ญ store"๋ผ๋Š” ํŒ๋‹จ์€ ๋ฐ์ดํ„ฐ์˜ ์†Œ์œ ๊ถŒ๊ณผ ์‹ ์„ ๋„ ๋ฌธ์ œ๋ฅผ ๋†“์นฉ๋‹ˆ๋‹ค.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋Š” ์ €์žฅ๋ณด๋‹ค ๋™๊ธฐํ™”๊ฐ€ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

Q2. staleTime๊ณผ gcTime์„ ๊ตฌ๋ถ„ํ•ด์„œ ์„ค๋ช…ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: staleTime์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ ์„ ํ•˜๋‹ค๊ณ  ๋ณด๋Š” ์‹œ๊ฐ„์ด๊ณ , gcTime์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์บ์‹œ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋ณด๊ด€ํ•˜๋Š” ์‹œ๊ฐ„์ด๋ผ ๋ชฉ์ ์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: ๋‘ ๊ฐ’์„ ํ˜ผ๋™ํ•˜๋ฉด ๋„ˆ๋ฌด ์ž์ฃผ refetch๋˜๊ฑฐ๋‚˜, ๋ฐ˜๋Œ€๋กœ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋ž˜ ๋ณด์ด๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. ๋ฉด์ ‘์—์„œ๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๊ณผ ๋„คํŠธ์›Œํฌ ๋น„์šฉ ์‚ฌ์ด์˜ ๊ท ํ˜•๊นŒ์ง€ ๋งํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: ๋‘˜ ๋‹ค "์บ์‹œ ์‹œ๊ฐ„"์ด๋ผ๊ณ ๋งŒ ๋‹ตํ•˜๋ฉด ์‹ค์ œ ์„ค์ • ์˜๋„๋ฅผ ์„ค๋ช…ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ์‹ ์„ ๋„์™€ ๋ณด๊ด€ ๊ธฐ๊ฐ„์€ ๋‹ค๋ฅธ ์ถ•์ž…๋‹ˆ๋‹ค.

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ๋Œ“๊ธ€ ์ž‘์„ฑ mutation ์„ฑ๊ณต ํ›„ ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์šด ์บ์‹œ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ๊ด€๋ จ query key๋ฅผ ๋ฌดํšจํ™”ํ•˜๊ฑฐ๋‚˜ optimistic update๋ฅผ ์ ์šฉํ•ด ๋ชฉ๋ก๊ณผ ์ƒ์„ธ ํ™”๋ฉด์˜ ์ผ๊ด€์„ฑ์„ ํšŒ๋ณตํ•œ๋‹ค

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: mutation์€ ์„œ๋ฒ„์— ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ค๊ณ , ๊ธฐ์กด ์บ์‹œ๋Š” ๊ทธ ์ˆœ๊ฐ„ ๋‚ก์•„์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค key๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›๋Š”์ง€ ์„ค๊ณ„ํ•ด์•ผ ์‚ฌ์šฉ์ž๋Š” ์ผ๊ด€๋œ ํ™”๋ฉด์„ ๋ด…๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: window.location.reload()๋Š” ๋ฌธ์ œ๋ฅผ ์ˆจ๊ธธ ๋ฟ, ์บ์‹œ ์ „๋žต์„ ์„ค๊ณ„ํ•œ ๋‹ต๋ณ€์ด ์•„๋‹™๋‹ˆ๋‹ค.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: mutation ํ›„์—๋Š” ์–ด๋–ค ์บ์‹œ๊ฐ€ ๋‚ก์•˜๋Š”์ง€ ๋ฌป์Šต๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด ๋งˆ์Œ๋Œ€๋กœ ์†Œ์œ ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋‹ค๋ฃจ๋˜ ์Šต๊ด€์„ ๋ฒ„๋ ธ๋‹ค. ์˜์ˆ˜๋„ค ๋Œ“๊ธ€ ๋ชฉ๋ก์ด ์ƒˆ๋กœ๊ณ ์นจํ•ด์•ผ๋งŒ ๋งž์•„ ๋ณด์˜€๋˜ ์ด์œ ๋„ ๊ฒฐ๊ตญ ์บ์‹œ๋ฅผ ์„ค๊ณ„ํ•˜์ง€ ์•Š๊ณ  ๊ฐ’๋งŒ ์ €์žฅํ•˜๋ ค ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ๋‹ค.

๐Ÿ’ก "์„œ๋ฒ„ ์ƒํƒœ๋Š” ๋“ค๊ณ  ์žˆ๋Š” ๊ฐ’์ด ์•„๋‹ˆ๋ผ, ์„œ๋ฒ„์™€ ๊ณ„์† ๋งž์ถฐ์•ผ ํ•˜๋Š” ์•ฝ์†์ด๋‹ค."

๋‹ค์Œ๋ถ€ํ„ฐ๋Š” API ์‘๋‹ต์„ store์— ๋„ฃ๊ธฐ ์ „์— "์ด ๋ฐ์ดํ„ฐ์˜ ์›๋ณธ์€ ์–ด๋””๊ณ , ์–ธ์ œ ๋‚ก์œผ๋ฉฐ, ์–ด๋–ค mutation์ด ์˜ํ–ฅ์„ ์ฃผ์ง€?"๋ฅผ ๋จผ์ € ์ ์–ด๋ด์•ผ๊ฒ ๋‹ค. ์ด์ œ ์˜์ฒ ์ด ์กฐ๊ธˆ์”ฉ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ๋ฐ”๊นฅ์˜ ์‹œ๊ฐ„๊นŒ์ง€ ๋ณด๊ฒŒ ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.