๐ก 04. ์์กด์ฑ ์๋ ์ฟผ๋ฆฌ (Dependent Queries)์ ๋ณ๋ ฌ ํ์นญ
๐ ๊ฐ์
ํญํฌ์(Waterfall) ํ์นญ์ ํผํ๋ ๋ฐฉ๋ฒ๊ณผ `enabled` ์ต์ ์ ํ์ฉํ์ฌ ์์กด์ ์ธ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค.
๐ ๋ชฉ์ฐจ
- โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "๋ฐ์ดํฐ ๋ ๊ฐ ๊ฐ์ ธ์ค๋๋ฐ ๋ก๋ฉ์ด ๋ ๋ฐฐ๋ก ๊ฑธ๋ ค์!"
- ๐ค ์ ์์์ผ ํ๋๊ฐ: ๋คํธ์ํฌ ๋ณ๋ชฉ ์ค์ด๊ธฐ
- 1. ๋์ ํญํฌ์(Waterfall) ํผํ๊ธฐ: ๋ณ๋ ฌ ํ์นญ(Parallel Queries)
- 2. ์์๊ฐ ์ค์ํ ๋:
enabled์ต์ ์ ๋ง๋ฒ (Dependent Queries) - ๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
"์์ฒ ๋, ์ ์ ์ ๋ณด๋ฅผ ๋ค ๋ถ๋ฌ์ฌ ๋๊น์ง ์ ๊ฒ์๊ธ ๋ชฉ๋ก API๋ ์์์กฐ์ฐจ ์ ํ๊ณ ์๊ฐ๋ฝ๋ง ๋นจ๊ณ ์๋์?"
โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "๋ฐ์ดํฐ ๋ ๊ฐ ๊ฐ์ ธ์ค๋๋ฐ ๋ก๋ฉ์ด ๋ ๋ฐฐ๋ก ๊ฑธ๋ ค์!"
(๊ธ์์ผ ์ค์ , ๋คํธ์ํฌ ํญ์ ๋ณด๋ฉฐ ๋จธ๋ฆฌ๋ฅผ ๋ฒ ๋ฒ ๊ธ๋ ์์ฒ )
๐ฃ ์์ฒ : ๋ฆฌ๋ ๋! ๋ง์ดํ์ด์ง๋ฅผ ๋ง๋ค๊ณ ์๋๋ฐ์. ์ ์ ๊ฐ์ธ์ ๋ณด(fetchUser)๋, ์ ์ ๊ฐ ์ด ๊ฒ์๊ธ ๋ชฉ๋ก(fetchPosts)์ ํ๋ฉด์ ๊ฐ์ด ๋์์ผ ํ๊ฑฐ๋ ์? ๋ ๋ค useQuery๋ก ์ ์งฐ๋๋ฐ, ๋ก๋ฉ์ด ์์ฒญ ์ค๋ ๊ฑธ๋ฆฌ๋ค์. ๋คํธ์ํฌ ํญ ๋ณด๋๊น ์ ์ ์ ๋ณด ๋ค ๋ฐ์์จ ๋ค์์ผ ๊ฒ์๊ธ API๊ฐ ๋ค๋ฆ๊ฒ ์ถ๋ฐํด์.
๐ฆ ์ํธ: ์์ฃผ ์ ํ์ ์ธ ํญํฌ์(Waterfall) ํ์นญ ํ์์ด๋ค์. ์์ฒ ๋์ด ๋ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ๋ก๋ฐ๋ก ๋ถ๋ ๋๋ผ๋, ์ปดํฌ๋ํธ ๋ ๋๋ง์ ๋ง์(Block) ๋๊ธฐ ๋๋ฌธ์ ๋ณ๋ ฌ(Parallel) ์ฒ๋ฆฌ๊ฐ ์ ๋ ๊ฒ๋๋ค. ๊ฒ๋ค๊ฐ ๋ฐ๋๋ก, ์ ์ id๊ฐ ์์ด์ผ๋ง ๊ฒ์๊ธ์ ๊ฐ์ ธ์ฌ ์ ์๋ ์์กด์ ์ธ ์ํฉ ์์๋ ์๋ฌด ๋๋ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ฉด ์ปดํ์ผ ์๋ฌ๋ 404 ์๋ฌ๊ฐ ๋๊ฒ ์ฃ .
๐ฃ ์์ฒ : ์... ๊ทธ๋ผ ์๋ก ์๊ด์๋ ์ ๋ค์ ๊ฐ์ด ์ถ๋ฐ์ํค๊ณ , ์๋ก ๊ผฌ์ธ ์ ๋ค์ ์์๋๋ก ์ถ๋ฐ์ํค๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ์ฃ ?!
๐ค ์ ์์์ผ ํ๋๊ฐ: ๋คํธ์ํฌ ๋ณ๋ชฉ ์ค์ด๊ธฐ
ํ๋์ ์น์์ ํ์ด์ง ํ๋๋ฅผ ๋์ธ ๋ API 3~4๊ฐ๋ฅผ ํธ์ถํ๋ ๊ฑด ๊ธฐ๋ณธ์
๋๋ค.
์ด 3๊ฐ์ API๊ฐ ์๋ก ์๋ฌด ๊ด๋ จ์ด ์๋๋ฐ๋ (์: ์๋จ ๋ฐฐ๋ ์ด๋ฏธ์ง, ์ฐ์ธก ์ถ์ฒ ์ํ, ๋ณธ๋ฌธ ๋ด์ฉ) ์์ฐจ์ ์ผ๋ก ํ๋์ฉ A ์๋ฃ -> B ์์ -> B ์๋ฃ -> C ์์ ๋๋ค๋ฉด ์ ์ ์ ์ธ๋ด์ฌ์ ๋ฐ๋ฅ๋ฉ๋๋ค.
๋ฐ๋ฉด, A์ ๊ฒฐ๊ณผ๋ฌผ(์ ์ id)์ด ์์ด์ผ๋ง B(ํด๋น ์ ์ ์ ์ฅ๋ฐ๊ตฌ๋)๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ ์๊ฒฉํ ์ํฉ๋ ์์ต๋๋ค.
React Query๋ ์ด ๋ ๊ฐ์ง ๋๋ ๋ง๋ฅผ ๋ณ๋ ฌ ๋ ๋๋ง ๊ณผ enabled ์ต์
์ด๋ผ๋ ๊ฐ๋ ฅํ ๋ฌด๊ธฐ๋ก ์ฐ์ํ๊ฒ ํด๊ฒฐํฉ๋๋ค.
1. ๋์ ํญํฌ์(Waterfall) ํผํ๊ธฐ: ๋ณ๋ ฌ ํ์นญ(Parallel Queries)
์์ฒ ์ด๊ฐ ์งฐ๋ ์ฝ๋๋ฅผ ๋ด ์๋ค.
// โ ์์ฒ ์ด์ ํญํฌ์ ์ฝ๋ (Waterfall)
function MyPage() {
const { data: user, isLoading: isUserLoading } = useQuery({ queryKey: ['user'], queryFn: fetchUser });
// ์ฌ๊ธฐ์ ๋ ๋๋ง์ด ๋งํ๋ค! (Return ์ณ๋ฒ๋ฆผ)
if (isUserLoading) return <Spinner />;
// โ ๏ธ ์์ชฝ์ ๋ก๋ฉ์ด ์ ๋๋๋ฉด, ์ด ์ฝ๋๋ ์์ ์คํ์กฐ์ฐจ(๋ง์ดํธ์กฐ์ฐจ) ์ ๋ฉ๋๋ค.
const { data: posts, isLoading: isPostsLoading } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
// return JSX...
}์ด ๋ฐฉ์์ useEffect ์์ ์ ์์ฌ์
๋๋ค. React Query๋ฅผ ์ธ ๋๋ ๋ชจ๋ ํ
์ ์๋ฌด ๋ฐฉํด ์์ด ์ต์๋จ์ ๋ณ๋ ฌ๋ก ๊น์๋๊ธฐ๋ง ํ๋ฉด ์์์ ๋์์ ์ถ๋ฐํฉ๋๋ค.
// โ
์ํธ ๋ฆฌ๋์ ๋์ ์ถ๋ฐ(๋ณ๋ ฌ) ์ฝ๋
function MyPage() {
// 1. ๋ ๋ค ๋์์ ๋คํธ์ํฌ ์ถ๋ฐ! (๋-๋ธ๋กํน)
const userQuery = useQuery({ queryKey: ['user'], queryFn: fetchUser });
const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
// 2. ๋ ๊ฐ ์ค ํ๋๋ผ๋ ๋ก๋ฉ ์ค์ด๋ฉด ์คํผ๋ ํ๋๋ง ๋์
const isLoading = userQuery.isLoading || postsQuery.isLoading;
if (isLoading) return <Spinner />;
// 3. ๋ ๋ค ๋ฌด์ฌํ ๋์ฐฉ ์ ํ๋ฉด ๋ ๋!
return <Profile user={userQuery.data} posts={postsQuery.data} />;
}๐ฅ ์ต์ ๋ฌธ๋ฒ:
useQueries(๋์ ๋ณ๋ ฌ ์ฟผ๋ฆฌ)
๋ง์ฝ ์ฟผ๋ฆฌ ๊ฐ์๊ฐ ์ ํด์ ธ ์์ง ์๊ณ , ๋ฐฐ์ด ์์๋งํผ ๋์ ์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ 10๊ฐ, 100๊ฐ ์ด์ผ ํ๋ค๋ฉดuseQueriesํ ์ ์ฌ์ฉํ์ธ์! (๋ฌธ์ ๊ตฌ์กฐ๊ฐ ํจ์ฌ ๊น๋ํด์ง๋๋ค.)
2. ์์๊ฐ ์ค์ํ ๋: enabled ์ต์
์ ๋ง๋ฒ (Dependent Queries)
๋ฐ๋๋ก ์๊ฐํด๋ด
์๋ค. ์ด๋ฒ์ fetchPosts API๊ฐ ์ ์ ์ userId ํ๋ผ๋ฏธํฐ ๋ฅผ ํ์๋ก ๋ฐ์์ผ๋ง ๋์ํ๋ API๋ผ๊ณ ๊ฐ์ ํด๋ณด์ฃ . ์์ฒ๋ผ ๋์์ ์ถ๋ฐ์ํค๋ฉด userId๊ฐ undefined์ธ ์ํ๋ก API๊ฐ ๋ ์๊ฐ์ 400 Bad Request๊ฐ ๋น๋๋ค.
์ด๋ React Query ์ต๊ณ ์ ์นํธํค์ธ enabled ์ต์
์ ์ฌ์ฉํฉ๋๋ค.
function MyProfile() {
// 1. ์ ์ ์ ๋ณด๋ฅผ ๋จผ์ ์๋ค.
const { data: user } = useQuery({
queryKey: ['user', 'me'],
queryFn: getUserByToken,
});
// 2. ๊ฒ์๊ธ์ ์ ์ ์ ๋ณด๊ฐ ์์ ๋๋ง ์๋ค!
const { data: posts } = useQuery({
queryKey: ['posts', user?.id], // user.id๊ฐ ์๊ธธ ๋ ๋์ ์ผ๋ก ํค๊ฐ ๋ณํจ
queryFn: () => fetchPostsByUser(user!.id),
// ๐ก ๋ง๋ฒ์ ํ ์ค: user.id๊ฐ ์กด์ฌํ (Truthy) ๋๋ง ์ด ์ฟผ๋ฆฌ๋ฅผ ํ์ฑํ(Enable)ํ๋ผ!
enabled: !user?.id,
});
return ( ... );
}enabled: false ์ํ์ธ ์ฟผ๋ฆฌ๋:
- ๋ง์ดํธ๋์ด๋ ์ ๋ ๋คํธ์ํฌ ์์ฒญ์ ๋ ๋ฆฌ์ง ์์ต๋๋ค.
isLoading์ํ๊ฐ ๋ฌดํ์ ์ง์๋๋ ๊ฒ์ด ์๋๋ผ,fetchStatus === 'idle'์ํ๋ก ์์ ํ ๋๊ธฐํฉ๋๋ค.- ์กฐ๊ฑด(
!user?.id)์ดtrue๋ก ๋ค๋ฐ๋๋ ์๊ฐ, ์ ์์ ๊นจ์ด๋ ๋งน๋ ฌํ๊ฒ ๋ฐ์ดํฐ๋ฅผ Fetch ํฉ๋๋ค.
๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
Q. ์์ฒ ์ด๋ ์ฌ์ฉ์ ๊ฒ์ ์ฐฝ์ ๋ง๋ค๊ณ ์์ต๋๋ค. ๊ฒ์์ด(keyword)๊ฐ 3๊ธ์ ๋ฏธ๋ง์ผ ๋๋ ์์ ๋ฐฑ์๋๋ก ๊ฒ์ API๋ฅผ ์์ง ์๊ณ ์ถ์ด ํฉ๋๋ค. React Query๋ก ์ด๋ฅผ ๊ตฌํํ ๊ฐ์ฅ ์ฐ์ํ ๋ฐฉ๋ฒ์ ๋ฌด์์
๋๊น?
- A)
queryFnํจ์ ์์์if (keyword.length < 3) return []์ฒ๋ฆฌํ๋ค. - B)
useEffect์์์ 3๊ธ์๊ฐ ๋์ผ๋ฉดqueryClient.prefetchQuery๋ฅผ ์๋ค. - C)
useQuery์ค์ ์์enabled: keyword.length >= 3์ ์ถ๊ฐํ๋ค.
โ
์ ๋ต: C
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
:
enabled์ต์ ์ ๋จ์ง "์์๊ฐ ๊ผฌ์ธ ์ฟผ๋ฆฌ"์๋ง ์ฐ๋ ๊ฒ์ด ์๋๋ผ, "ํน์ ์กฐ๊ฑด์ด ๋ง์กฑ๋ ๋๋ง ์ฟผ๋ฆฌ๋ฅผ ์ผ๊ณ ์ฐ๊ฒ ๋ค" ๋ ์ ์ธ์ ์ ์ด ์ฅ์น์ ๋๋ค. ๊ฒ์์ด ๊ธธ์ด๊ฐ ์กฐ๊ฑด์ ๋ถํฉํ ๋๋ง ๋คํธ์ํฌ๊ฐ ํ์ฑํ๋๋ฉฐ, ๋ถํฉํ์ง ์์ผ๋ฉดfetchStatus๊ฐidle๋ก ์ ์ง๋๋ฉด์ ์ธ๋ฐ์๋ ๋คํธ์ํฌ ๋ญ๋น๋ฅผ ์๋ฒฝํ ์ฐจ๋จํฉ๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, A๋ฒ์ฒ๋ผ
queryFn์์์ ๋ฆฌํดํด๋ฒ๋ฆฌ๋ฉด ๋คํธ์ํฌ๋ ์ ํ์ง์ธ์ React Query๋ '์ ๊ฒ์ ์๋ฃ๋๊ตฌ๋!' ํ๊ณ ์ฑ๊ณต(success) ์ํ๋ก ๋น ๋ฐฐ์ด ์บ์๋ฅผ ๋จ๊ฒจ๋ฒ๋ ค์. ์์ ์๋์กฐ์ฐจ ์ ๊ฑธ๋ฆฌ๊ฒ ํ๋ ค๋ฉด ์์ง(Query) ๋ฐ์์enabled๋ก ์ค์์น๋ฅผ ๋ด๋ ค์ผ ํฉ๋๋ค!" - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ์ฟผ๋ฆฌ ๋ฐ์ฌ๋ฅผ ์ฐธ์์ผ ํ ๋, ๋ชฉ์ค ๊ฝ ์ฅ๋
enabled: ์กฐ๊ฑด์!
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์ ์ ์ ๋ณด๋ฅผ ๋ฐ์ ๋ค์๋ง ํด๋น ์ ์ ์ ๊ฒ์๊ธ์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. TanStack Query์์ ๊ฐ์ฅ ์์ฐ์ค๋ฌ์ด ์ ์ด ๋ฐฉ๋ฒ์ ๋ฌด์์ธ๊ฐ์?
โ
์ ๋ต: ๋ ๋ฒ์งธ ์ฟผ๋ฆฌ์ queryKey์ userId๋ฅผ ํฌํจํ๊ณ enabled: !userId๋ก ์คํ ์กฐ๊ฑด์ ๋ก๋๋ค.
๐ก ์์ธ ํด์ค:
์์กด์ฑ ์๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ด ์ค๋น๋๊ธฐ ์ ์๋ ๋๊ธฐํด์ผ ํฉ๋๋ค. queryFn ์์์ if (!userId)๋ก ๋ง๋ ๋ฐฉ์์ ์คํ ์กฐ๊ฑด๊ณผ ์บ์ ํค๊ฐ ํ๋ ค์ง๋๋ค. ํค์ enabled๋ฅผ ํจ๊ป ์ค๊ณํ๋ฉด ๋ฐ์ดํฐ ํ๋ฆ์ด ์ฝ๋ ๋ฆฌ๋ทฐ์์ ๋ฐ๋ก ๋ณด์
๋๋ค.
Q2. ์ฒซ ๋ฒ์งธ ์ฟผ๋ฆฌ๊ฐ ๋ก๋ฉ ์ค์ด๋ฉด ๋ฐ๋ก returnํ๋ ์ฝ๋๊ฐ ๋ณ๋ ฌ ํ์นญ์ ๋ง์น๋ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
โ ์ ๋ต: ์๋์ชฝ ์ฟผ๋ฆฌ ํ ์ด ์คํ๋ ๊ธฐํ๋ฅผ ์ป์ง ๋ชปํด ๋คํธ์ํฌ ์์ฒญ์ด ์์ฐจ์ ์ผ๋ก ๋ฐ๋ฆฌ๋ ์ํฐํด์ด ์๊ธฐ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๐ก ์์ธ ํด์ค:
React ํ
์ ๋ ๋๋ง ์ค ํธ์ถ๋์ด์ผ ํฉ๋๋ค. ์ฒซ ์ฟผ๋ฆฌ ๋ก๋ฉ์์ ์ปดํฌ๋ํธ๊ฐ ์กฐ๊ธฐ ๋ฐํ๋๋ฉด ๋ค์ ์ฟผ๋ฆฌ๋ ์์์กฐ์ฐจ ๋ชป ํฉ๋๋ค. ์๋ก ๋
๋ฆฝ์ธ ๋ฐ์ดํฐ๋ผ๋ฉด ์ฟผ๋ฆฌ๋ฅผ ๋ชจ๋ ๋จผ์ ์ ์ธํ๊ณ , ๋ ๋๋ง ๋ถ๊ธฐ๋ง ๋์ค์ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
Q3. ์์ฒ ์ด์ ํ
์คํธ ํ์: ์ ํ๋ ํ๊ทธ ID ๋ฐฐ์ด์ด [1, 2, 3]์ฒ๋ผ ๋งค๋ฒ ๋ฌ๋ผ์ง๊ณ , ๊ฐ ํ๊ทธ ์์ธ๋ฅผ ๋์์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ์ด๋ค ํ
์ด ์ ์ ํ๊ฐ์?
โ
์ ๋ต: useQueries๋ก ๋์ ๊ฐ์์ ์ฟผ๋ฆฌ ์ต์
๋ฐฐ์ด์ ๋ง๋ค์ด ๋ณ๋ ฌ ์คํํฉ๋๋ค.
๐ก ์์ธ ํด์ค:
๊ณ ์ ๋ 2~3๊ฐ ์ฟผ๋ฆฌ๋ useQuery๋ฅผ ๋๋ํ ๋ ์ ์์ง๋ง, ๊ฐ์๊ฐ ๋ฐฐ์ด ๊ธธ์ด์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ฉด useQueries๊ฐ ๋ง์ต๋๋ค. ํต์ฌ์ โ์์กด ๊ด๊ณ๊ฐ ์๋๊ฐ, ๋
๋ฆฝ ๋ณ๋ ฌ์ธ๊ฐ, ๋์ ๊ฐ์์ธ๊ฐโ๋ฅผ ๋จผ์ ๋๋๋ ๊ฒ์
๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์... ๋ ์ด๋๊น์ง ์ฟผ๋ฆฌ ํ๋ ๋ถ๋ ๋ค๊ณ ๊ทธ ๋ฐ์๋ค if (isLoading) return ์น๋ฉด์ ๊ธธ๋งํ๊ณ ์์๋ค. ๋ค๋ฅธ API๋ค์ด ๋ฉ ๋๋ฆฌ๊ณ ๊ธฐ๋ค๋ฆฌ๋ ์ค๋ ๋ชจ๋ฅด๊ณ ํ๋ก ํธ์๋ ์ ์ด๋ ๊ฒ ๋๋ฆฌ๋ ๋ฐฑ์๋ ํ๋ง ํ๋ค (์์ ๋ ์ฃ์ก...)
๐ก ์ค๋์ ๊ตํ: "์๋ก ์๊ด์๋ ์ฟผ๋ฆฌ๋ ๋๋ํ ์จ์ ๋ธ๋ผ์ฐ์ ์ ๋์์ฑ(๋ณ๋ ฌ) ์ฑ๋ฅ์ ๊ทนํ์ผ๋ก ๋ฝ์๋จน๊ณ , ์ ๋ฐ์ดํฐ ๊ฒฐ๊ณผ๊ฐ ๊ผญ ํ์ํ ์ฟผ๋ฆฌ๋ง
enabled์ต์ ์ผ๋ก ์ค๋งํธํ๊ฒ ๋ชฉ์ค์ ์ฑ์๋์!"
์ฌ์ค enabled ์ต์
์ฒ์ ๋ดค์ ๋ "์ด๊ฑด disabled์ ๋ฐ๋๋ง์ด๋๊น ํญ์ true๊ฒ ์ง?" ํ๊ณ ์ง๋์ณค๋๋ฐ ์ด๊ฒ ๋ฐ์ดํฐ ํ์นญ์ ์์(Dependency) ๋ฅผ ๋ณด์ฅํด์ฃผ๋ ์์ ์ ์ธ ๋๊ตฌ์ผ ์ค์ด์ผ... ๋น์ฅ ๋ด์ผ ๊ฐ์ ์ฐ์ ํ์นญ(chain fetching) ์ผ์ด๋ฌ๋ ๋ ๊ฑฐ์ ์ฝ๋๋ค ๋ค enabled ํ๋๋ก ์ ๋ฆฌํด์ผ๊ฒ ๋ค!