๐ก 02. ํต์ฌ ๋ฐ์ดํฐ ํ์นญ: `useQuery` ์๋ฒฝ ๊ฐ์ด๋
๐ ๊ฐ์
Query Key์ ๋ฐฐ์ด ์ปจ๋ฒค์ ๋ถํฐ status์ fetchStatus์ ์ฐจ์ด๊น์ง, useQuery์ ๊ธฐ๋ณธ ๋์ ์๋ฆฌ๋ฅผ ์๋ฒฝํ๊ฒ ํต์ ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค.
๐ ๋ชฉ์ฐจ
- โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "์คํผ๋๊ฐ ๋์๊ฐ๋ค ๋ง๋ค ํ๋๋ฐ์?"
- ๐ค ์ ์์์ผ ํ๋๊ฐ: ์ฟผ๋ฆฌ๋ฅผ 100% ํต์ ํ๊ธฐ ์ํด
- 1. Query Key: ๋ฐฐ์ด ์ปจ๋ฒค์ ๊ณผ ์์กด์ฑ์ ๋ง๋ฒ
- 2. Promise๋ฅผ ์บ์ฑํ๋ Query Function (
queryFn) - 3. ๋ก๋ฉ ์ํ์ ์ง์ค:
statusvsfetchStatus(v4+) - ๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
"์์ฒ ๋, ๋ฐ์ดํฐ๊ฐ ๋ก๋ฉ ์ค์ผ ๋ ์คํผ๋๊ฐ ์ ๋ฌ๋ค๊ณ ์?
status๋fetchStatus์ ์ฐจ์ด๋ฅผ ์์ง ๋ชจ๋ฅด์๋๊ตฐ์."
โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "์คํผ๋๊ฐ ๋์๊ฐ๋ค ๋ง๋ค ํ๋๋ฐ์?"
(์์์ผ ์ค์ , ์๋ก๊ณ ์นจ ๋ฒํผ์ ์ฐํํ๋ฉฐ ํ์จ ์ฌ๋ ์์ฒ )
๐ฃ ์์ฒ : ์... ๋ฏธ์น๊ฒ ๋ค. ์ฒ์์ ํ์ด์ง ๋ค์ด์ฌ ๋๋ "๋ก๋ฉ ์ค..." ์คํผ๋๊ฐ ์ ๋๊ฑฐ๋ ์? ๊ทผ๋ฐ ๋๊ฐ์ ํ์ด์ง์ ๋ค์ ๋ค์ด์ฌ ๋๋ ์คํผ๋๊ฐ 0.1์ด ๋ด๋ค๊ฐ ํ
์ฌ๋ผ์ ธ์ ํ๋ฉด์ด ํฝํฝ ๋๊ฒจ ๋ณด์ฌ์. ๊ทธ๋ฆฌ๊ณ ์ ์ ๋ฆฌ์คํธ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ ํค๋ฅผ ๊ทธ๋ฅ ๋ฌธ์์ด "users" ํ๋ ๋ก ์ผ๋๋ ์์ ๋์ด ๋ฐฑ์๋ ๋ถํ ์ฌํ๋ค๊ณ ๋ญ๋ผ๊ณ ํ์๋ค์.
๐ฆ ์ํธ: ์์ฒ ๋, Query Key๋ฅผ ๋ฌธ์์ด๋ก๋ง ๊ด๋ฆฌํ๋ฉด ๋์ค์ ์บ์ ํํธํ๋ ์๋ฑํ ์บ์ ํํธ(Cache Hit)๊ฐ ๋ฐ์ํด์. ๊ฒ๋ค๊ฐ ๋ฐฉ๊ธ ๋ง์ํ์ '์คํผ๋ ํฝํฝ ํ์'์ isLoading๊ณผ isFetching์ ์ฐจ์ด๋ฅผ ์ ๋๋ก ๋ชฐ๋ผ์ ๋ฐ์ํ๋ ์ ํ์ ์ธ ์ํฐ ํจํด์
๋๋ค.
๐ฃ ์์ฒ : ํ! isLoading์ด ๊ทธ๋ฅ fetch ๋ ๋ true ๋๋ ๊ฑฐ ์๋์์? ๋ ๊ฐ๊ฐ ์ด๋ป๊ฒ ๋ค๋ฅด์ฃ ?
๐ค ์ ์์์ผ ํ๋๊ฐ: ์ฟผ๋ฆฌ๋ฅผ 100% ํต์ ํ๊ธฐ ์ํด
๐ฆ ์ํธ: useQuery๋ ๋จ์ํ "Promise ์ฃผ๋ฉด ๋ฐ์ดํฐ ์ค๊ฒ" ์์ค์ ํ
์ด ์๋๋๋ค. ์ด ์ฑ ์ ์ฒด์ ๋น๋๊ธฐ ๋คํธ์ํฌ ์คํผ๋๋ฅผ ์งํํ๋ ๊ตํต ์๊ฒฝ์ด์์.
- Query Key: ์ด ๋ฐ์ดํฐํ(๋์ฅ๋ถ)์ '์ด๋ฆํ'์ด์, ๊ทธ ๋ฐ์ดํฐ์ ์์กด์ฑ ๋ฐฐ์ด(Dependency Array) ์ญํ ์ ๋์์ ์ํํฉ๋๋ค.
- ์ํ์ ์ด์ํ: React Query v4(TanStack Query)๋ถํฐ๋ ๋ฐ์ดํฐ์ ์ ๋ฌด(
status)์, ๋ฐฑ๊ทธ๋ผ์ด๋ ๋คํธ์ํฌ์ ํ๋ ์ ๋ฌด(fetchStatus)๋ฅผ ์์ ํ ๋ถ๋ฆฌํ์ต๋๋ค. ์ด๊ฑธ ๊ตฌ๋ถํ์ง ๋ชปํ๋ฉด ์คํ๋ผ์ธ ์ํฉ์ด๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ ๋ฐ์ดํธ ์ํฉ์์ ์ฌ์ฉ์์๊ฒ ์๋ชป๋ ํผ๋๋ฐฑ์ ์ฃผ๊ฒ ๋ฉ๋๋ค.
์ด ๊ธฐ๋ณธ์ ์ธ ์๋ฆฌ๋ฅผ ๋์ถฉ ๋์ด๊ฐ๋ฉด ๋์ค์ ๋ฌดํ ์คํฌ๋กค, ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ํ ๋ ์บ์ ๊ด๋ฆฌ์์ ๋ฐ๋์ ์ฌ๊ณ ๊ฐ ํฐ์ง๋๋ค. ์ ๋๋ก ํํค์ณ๋ด ์๋ค.
1. Query Key: ๋ฐฐ์ด ์ปจ๋ฒค์ ๊ณผ ์์กด์ฑ์ ๋ง๋ฒ
๊ณผ๊ฑฐ(v3 ์ด์ )์๋ Query Key๋ฅผ ๋ฌธ์์ด ํ๋๋ง ๋์ง ์๋ ์์์ต๋๋ค. (์: useQuery('users', fetchUsers)). ํ์ง๋ง ์ง๊ธ์ ๋ฌด์กฐ๊ฑด ๋ฐฐ์ด(Array) ํํ๋ก๋ง ์ ์ธํด์ผ ํฉ๋๋ค. ์ ๊ทธ๋ด๊น์?
// โ ์์ฒ ์ด์ ์ํํ ์ฝ๋
const { data } = useQuery(['todos', state], () => fetchTodos(state));
// โ
์ํธ ๋ฆฌ๋์ ๊น๋ํ๊ณ ํ์ฅ ๋ถ๊ฐ๋ฅํ ์ฝ๋
// ๊ฐ์ฒด ์์ ๋ฃ์ด์ ์์ ๋ฌด๊ด & ํ์ฅ์ฑ ํ๋ณด
const { data } = useQuery({
queryKey: ['todos', { status: state }],
queryFn: () => fetchTodos(state)
});๋ฐฐ์ด์ ์ฐ๋ ์ง์ง ์ด์ : ์์กด์ฑ ๋ฐฐ์ด
useEffect์ ๋ ๋ฒ์งธ ์ธ์์ธ ์์กด์ฑ ๋ฐฐ์ด์ ์๊ฐํด๋ณด์ธ์. React Query์ Query Key๋ ์ ํํ ๊ทธ ์ญํ ์ ์ํํฉ๋๋ค. ๋ฐฐ์ด ๋ด๋ถ์ ์์(์: state ๋ณ์)๊ฐ ๋ณ๊ฒฝ๋๋ฉด, React Query๋ ์ฆ๊ฐ์ ์ผ๋ก "์ด? ํค๊ฐ ๋ฐ๋์๋ค? ๊ทธ๋ผ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ผ์ง(Refetch)!" ๋ผ๊ณ ํ๋จํ์ฌ ์๋ก์ด ๋คํธ์ํฌ ์์ฒญ์ ๋ ๋ฆฝ๋๋ค.
๐ก ์ฌํ ํ: Query Key ๋ฐฐ์ด ๋ด๋ถ์ ์์ดํ ์์๋ ์ค์ํ ๊น์?
- ๋ฌธ์์ด ๋ฐฐ์ด ์์์ ์์๋ ๋ฌด์กฐ๊ฑด ์ผ์นํด์ผ ๊ฐ์ ์บ์๋ก ์นฉ๋๋ค. (
['todos', 'all']๊ณผ['all', 'todos']๋ ์์ ๋ค๋ฅธ ์บ์์ ๋๋ค.)- ๋ฐ๋ฉด, ๊ฐ์ฒด ๋ด๋ถ์ ํค ์์ ๋ ๋ฌด๊ดํฉ๋๋ค. (
[{ status: 'all', page: 1 }]๊ณผ[{ page: 1, status: 'all' }]์ ๋์ผํ ์บ์๋ก ์ธ์ํ์ฌ ๋ญ๋น๋ฅผ ๋ง์์ค๋๋ค.)
2. Promise๋ฅผ ์บ์ฑํ๋ Query Function (queryFn)
queryFn์ ๋ฐ๋์ Promise๋ฅผ ๋ฐํํ๋ ํจ์ ์ฌ์ผ ํฉ๋๋ค.
๋๋ถ๋ถ์ ์ฃผ๋์ด ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ค์ด ์ฌ๊ธฐ์ ๊ฒช๋ ์ค๋ฅ๋ "ํจ์ ์์ฒด๋ฅผ ๋์ง์ง ์๊ณ , ํจ์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋์ง๋" ์ค์์
๋๋ค.
// โ ์์ฒ ์ด์ ํํ ์ค์ 1 (๋จ์ผ ์คํ ํ ํญ๋ฐ)
useQuery({
queryKey: ['user', 1],
queryFn: fetchUser(1) // ๐ฅ ์ด๊ฑด ํจ์๊ฐ ์๋๋ผ '์คํ๋ Promise ๊ฒฐ๊ณผ๋ฌผ'์
๋๋ค! ๋ ๋๋ง๋ง๋ค ์ฝ๋ฐฑ ์ง์ฅ ๋ฐ์.
});
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ์ (๊ณ ์ฐจ ํจ์ ํํ๋ก ์ ๋ฌ)
useQuery({
queryKey: ['user', 1],
queryFn: () => fetchUser(1)
});3. ๋ก๋ฉ ์ํ์ ์ง์ค: status vs fetchStatus (v4+)
๊ฐ์ฅ ์ค์ํฉ๋๋ค. ์์ฒ ์ด๊ฐ ๊ฒช์๋ "์คํผ๋๊ฐ ๋๋ค ๋ง๋ค ํ๋ค"๋ ๋ฌธ์ ๋ ์ฌ๊ธฐ์ ๊ธฐ์ธํฉ๋๋ค.
TanStack Query๋ ์ํ๋ฅผ ๋ ๊ฐ์ง ์ถ ์ผ๋ก ๋๋์ด์ ๋ด ๋๋ค.
status(๋ฐ์ดํฐ๊ฐ ์๋๊ฐ?)pending(์ด์ ์๋loading): ๋ด ์บ์์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๊ฐ ๋จ 1๋ฐ์ดํธ๋ ์๋ค! ์ต์ด ํ๋ ๋ก๋ฉ.error: ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ค ์๋ฌ ๋ฌ๋ค.success: ๋ด ์บ์์ ์ฑ๊ณต์ ์ธ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์๋ค.
fetchStatus(๋คํธ์ํฌ๊ฐ ๋๊ณ ์๋๊ฐ?)fetching: ๋ฐฑ๊ทธ๋ผ์ด๋๋ , ํฌ๊ทธ๋ผ์ด๋๋ ํ์ฌfetchํจ์๊ฐ ๋บ๋บ์ด ๋๋ ์ค์ด๋ค.paused: ๋คํธ์ํฌ๊ฐ ๋๊ฒผ๋ค(์คํ๋ผ์ธ).idle: ์ง๊ธ ์๋ฌด๊ฒ๋ ์ ๋ค๊ณ ์กฐ์ฉํ ์ฌ๋ ์ค์ด๋ค.
๊ฒฐ๋ก : ์คํผ๋๋ ๋๋์ฒด ์ธ์ ๋์์ผ ํ๋์?
๋ง์ฝ ์์ฒ ์ด๊ฐ isLoading(์ฆ, status === 'pending')์ผ ๋๋ง ์ ์ฒด ํ๋ฉด ์คํผ๋๋ฅผ ๋์ด๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ผ์ด ์ผ์ด๋ฉ๋๋ค:
- ์ฒซ ์ง์
(์บ์ ์์): ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฏ๋ก
isLoading์true. ์คํผ๋ ๋น๊ธ๋น๊ธ. (์ ์) - ๋ ๋ฒ์งธ ์ง์
(์บ์ ์์): ๋ฐ์ดํฐ๋ฅผ ์ด๋ฏธ ๊ฐ๊ณ ์์ด์ ๋ฐ๋ก ๋ณด์ฌ์ฃผ๋ฏ๋ก
isLoading์false์ ๋๋ค. ํ์ง๋ง ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ํ์ธ ์ค(isFetching)์ด์ฃ . - ์ด ๊ฒฝ์ฐ ์คํผ๋๊ฐ ์ ๋น๋๋ค. ๊ทธ๋์ ์ ์ ๋์๋ ๋ฒ๊ฐ์ฒ๋ผ ์ด์ ๋ฐ์ดํฐ๊ฐ ๋ณด์๋ค๊ฐ(์บ์ ํํธ), ๋ฐฑ๊ทธ๋ผ์ด๋ ํ์นญ์ด ๋๋๋ ์๊ฐ 0.5์ด ๋ค์ ์ค๋ฅด๋ฅต ์ต์ ๋ฐ์ดํฐ๋ก ๋ณ๊ฒฝ๋๋ ๋ง๋ฒ์ ๋ณด๊ฒ ๋ฉ๋๋ค.
const { data, isLoading, isFetching } = useQuery({ ... });
// ๐ก ํ๋ ๋ก๋ฉ ์คํผ๋ (ํ๋ฉด ์ ์ฒด ๋ฎ๊ธฐ): ๋ด ์์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๊ฐ ์์ ์์ ๋๋ง!
if (isLoading) return <FullPageSkeleton />;
// ๐ต ์ํํธ ๋ก๋ฉ ์คํผ๋ (์๋จ ๊ตฌ์ ์ชผ๊ทธ๋งฃ๊ฒ): ์์ ๋ฐ์ดํฐ๋ ์๋๋ฐ, ๋ท๋จ์์ ๊ฐฑ์ ์ค์ผ ๋!
return (
<div>
{isFetching && <SmallSpinner />} {/* ํ๋ฉด ์๋จ ๊ทํ์ด์ ์ด์ง */}
<ul>{data.map(item => <Item key={item.id} />)}</ul>
</div>
);์์ฒ ์, ์ด์ ์คํผ๋ ์ด๋ป๊ฒ ๋์์ผ ๋๋์ง ์๊ฒ ์ง? ๋ฌด์กฐ๊ฑด ๋ฎ๋ ๊ฒ ๋ฅ์ฌ๊ฐ ์๋๋ค!
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์... isLoading ํ๋๋ก ์ ์ฒด ํ๋ฉด์ ๋ค ๋ฎ์ด๋ฒ๋ฆฌ๋๊น ๋ฌดํ ๋ฆฌ๋ ๋๋ง์ด๋ ํ๋ฉด ๋ฒ์ฉ์์ด ์๊ฒผ๋ ๊ฑฐ๊ตฌ๋. ๋ฐ์ดํฐ๊ฐ ์ด๋ฏธ ์๋๋ฐ ์ ํ๋ฉด ์ ์ฒด๋ฅผ ๋ก๋ฉ์ผ๋ก ๋ฎ๋๋ ์์(UX ๋์์ธ) ๋์ ์์๋ฆฌ๊ฐ ์ด์ ์ผ ์ดํด๋๋ค.
๐ก ์ค๋์ ๊ตํ: "
isLoading(๋ฐ์ดํฐ ์์)๊ณผisFetching(๋คํธ์ํฌ ๋๋ ์ค)์ ๊ตฌ๋ถํ๋ผ. ์ด๊ธฐ ์ง์ ์ ๋ผ๋(Skeleton)๋ก, ์ฌ๋ฐฉ๋ฌธ ์์๋ ์๋จ ์ฌ๋งํ ์คํผ๋๋ก ์ฐ์ํ๊ฒ UX๋ฅผ ์ค๊ณํ์!"
๋ด์ผ ์ถ๊ทผํ์๋ง์ isLoading ํ๋๋ง ์ฐ๋ ์ปดํฌ๋ํธ๋ค ์น ๋ค ์ฐพ์์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์คํผ๋์ฉ ์ปดํฌ๋ํธ ์กฐ๊ฐ์ผ๋ก ๋ถ๋ฆฌํด๋ด์ผ์ง. ์ค๋์ ๊ทธ๋๋ ๋ ํผ๋ฌ๋ค. ๋ทํ๋ฆญ์ค ๋ด
์ฌ๋ ์ด ๋คํ ๋ณด๋ฉด์ ๋งฅ์ฃผ๋ ํ ์บ ํด์ผ์ง! ๐บ
๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
Q. ๋ค์๊ณผ ๊ฐ์ด ์นดํ ๊ณ ๋ฆฌ๋ณ ๋ด์ค๋ฅผ ์กฐํํ๋ ์ฝ๋๊ฐ ์์ต๋๋ค. ์ฌ์ฉ์๊ฐ '์ ์น' ํญ์์ '๊ฒฝ์ ' ํญ์ผ๋ก ์ด๋ํ ๋, ์ฝ๋๊ฐ ์ต์ '๊ฒฝ์ ' ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ๋ค์ ๊ฐ์ ธ์ค๊ฒ(Refetching) ํ๋ ค๋ฉด ์ด๋ป๊ฒ ์ค๊ณํด์ผ ํ ๊น์?
// ๋ฌธ์ ์ํฉ
const [category, setCategory] = useState('politics');
const { data } = useQuery({
queryKey: ['news'],
queryFn: () => fetchNews(category)
});โ
์ ๋ต: Query Key ๋ฐฐ์ด ์์ category ๋ณ์๋ฅผ ์ถ๊ฐํ์ฌ ์์กด์ฑ ๋ฐฐ์ด์ฒ๋ผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
const { data } = useQuery({
queryKey: ['news', category], // ์ด ๋ถ๋ถ!
queryFn: () => fetchNews(category)
});๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: React Query๋
queryFn๋ด๋ถ์ ์์กด์ฑ์ด ๋ฐ๋์๋์ง ์ ์ ์๋ ๋ง๋ฒ์ด ์์ต๋๋ค. ์ค์งqueryKey๋ฐฐ์ด์ ์ด๋ค ์์๋ค์ด ๋ค์ด์๋์ง๋ง ๊ฐ์ํฉ๋๋ค.category์ํ๊ฐ์ด ๋ฐ๋ ๋ ์ฟผ๋ฆฌ๊ฐ ๋ค์ ์คํ๋๋๋ก ๋ง๋ค๋ ค๋ฉด ๋ฌด์กฐ๊ฑด ๊ทธ๊ฒ์queryKey๋ฐฐ์ด ์์๋ก ๋ฃ์ด์ฃผ์ด์ผ ํฉ๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก React Query๋ฅผ ์ด์ฉํ ์ํ ๋๊ธฐํ์ ๊ธฐ๋ณธ์ ๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋,
queryFn์์category๋ณ์๋ฅผ ์๋ฌด๋ฆฌ ๋ฃ์ด๋๋,queryKey๋ฐฐ์ด ์ด๋ฆํ๊ฐ ๊ทธ๋๋ก['news']ํ๋๋ฟ์ด๋ผ๋ฉด ๋ฆฌ์กํธ ์ฟผ๋ฆฌ๋ '์ด? ํค๊ฐ ๋๊ฐ๋ค? ๋ฐฉ๊ธ ์ ์น ๋ด์ค ์บ์ํด ๋ ๊ฑฐ ๊ทธ๋๋ก ๋์ ธ์ค๊ฒ~' ํ๊ณ ๋์ด๊ฐ ๋ฒ๋ ค์. ์ ์น ๋๋ฅด๊ณ ๊ฒฝ์ ๋๋ ๋๋ฐ ํ๋ฉด์ ๊ณ์ ์ ์น๋ฉด๋ง ๋ณด์ผ ๊ฒ๋๋ค!" - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ์ฟผ๋ฆฌ ํ์ ์์์ ์ฌ์ฉํ๋ ๋ชจ๋ ๋ณ์(๋งค๊ฐ๋ณ์)๋ ์ฟผ๋ฆฌ ํค ๋ฐฐ์ด์ ์ธ์ง๋ก ํจ๊ป ๋ฌถ์ด๋ผ!