๐ก 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 ํฉ๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์... ๋ ์ด๋๊น์ง ์ฟผ๋ฆฌ ํ๋ ๋ถ๋ ๋ค๊ณ ๊ทธ ๋ฐ์๋ค if (isLoading) return ์น๋ฉด์ ๊ธธ๋งํ๊ณ ์์๋ค. ๋ค๋ฅธ API๋ค์ด ๋ฉ ๋๋ฆฌ๊ณ ๊ธฐ๋ค๋ฆฌ๋ ์ค๋ ๋ชจ๋ฅด๊ณ ํ๋ก ํธ์๋ ์ ์ด๋ ๊ฒ ๋๋ฆฌ๋ ๋ฐฑ์๋ ํ๋ง ํ๋ค ใ
ใ
ใ
(์์ ๋ ์ฃ์ก...)
๐ก ์ค๋์ ๊ตํ: "์๋ก ์๊ด์๋ ์ฟผ๋ฆฌ๋ ๋๋ํ ์จ์ ๋ธ๋ผ์ฐ์ ์ ๋์์ฑ(๋ณ๋ ฌ) ์ฑ๋ฅ์ ๊ทนํ์ผ๋ก ๋ฝ์๋จน๊ณ , ์ ๋ฐ์ดํฐ ๊ฒฐ๊ณผ๊ฐ ๊ผญ ํ์ํ ์ฟผ๋ฆฌ๋ง
enabled์ต์ ์ผ๋ก ์ค๋งํธํ๊ฒ ๋ชฉ์ค์ ์ฑ์๋์!"
์ฌ์ค enabled ์ต์
์ฒ์ ๋ดค์ ๋ "์ด๊ฑด disabled์ ๋ฐ๋๋ง์ด๋๊น ํญ์ true๊ฒ ์ง?" ํ๊ณ ์ง๋์ณค๋๋ฐ ์ด๊ฒ ๋ฐ์ดํฐ ํ์นญ์ ์์(Dependency) ๋ฅผ ๋ณด์ฅํด์ฃผ๋ ์๋ฒฝํ ๋๊ตฌ์ผ ์ค์ด์ผ... ๋น์ฅ ๋ด์ผ ๊ฐ์ ์ฐ์ ํ์นญ(chain fetching) ์ผ์ด๋ฌ๋ ๋ ๊ฑฐ์ ์ฝ๋๋ค ๋ค enabled ํ๋๋ก ๋ฐ์ด ๋ด์ค์ผ๊ฒ ๋ค!! ๐
๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (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: ์กฐ๊ฑด์!