๐ก 05. ๋ฐ์ดํฐ ๋ณ๊ฒฝ๊ณผ ์บ์ ๋ฌดํจํ: `useMutation`๊ณผ `invalidateQueries`
๐ ๊ฐ์
์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ Mutation์ ๊ธฐ์ด์, ์์ ํ ํด๋ผ์ด์ธํธ ์บ์๋ฅผ ์ค๋งํธํ๊ฒ ๊ฐฑ์ (๋ฌดํจํ)ํ๋ invalidateQueries์ ์ค๊ณ ์ฒ ํ์ ๋ฐฐ์๋๋ค.
๐ ๋ชฉ์ฐจ
- โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "๋ฐ์ดํฐ๋ ๋ฐ๊ฟจ๋๋ฐ ํ๋ฉด์ด ์ ๋ฐ๋์ด์!"
- ๐ค ์ ์์์ผ ํ๋๊ฐ: ์บ์ ์๋ฒ ๋๊ธฐํ์ ๋ณธ์ง
- 1. ์๋ฒ ํต์ ์ ๋ง๋ฒ์ฌ:
useMutation - 2. ๋ถ์์ผ๋ฉด ์๋ก ๊ณ ์นจํ๋ผ:
invalidateQueries - ๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
"์์ฒ ๋, ์๋ฒ์ ์ข์์ ๋ฐ์์ ์ ๋๋๋ฐ ์ ํ๋ฉด์ ์๋ก๊ณ ์นจ์ ๋๋ฌ์ผ๋ง ํํธ๊ฐ ๋นจ๊ฐ๊ฒ ๋ณํ์ฃ ?"
โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "๋ฐ์ดํฐ๋ ๋ฐ๊ฟจ๋๋ฐ ํ๋ฉด์ด ์ ๋ฐ๋์ด์!"
(ํ์์ผ ์คํ, F5(์๋ก๊ณ ์นจ) ํค๋ณด๋ ์ท๊ฑด์ ์น๊ธฐ ์ง์ ์ธ ์์ฒ )
๐ฃ ์์ฒ : ์ ๋ฆฌ๋ ๋! ๋ฐฉ๊ธ fetch๋ก '๊ฒ์๊ธ ์ญ์ ํ๊ธฐ' API ์๊ณ 200 OK ์ฝ์ ์ฐํ๋ ๊ฑฐ๊น์ง ์๋ฒฝํ๊ฒ ํ์ธํ๊ฑฐ๋ ์? ๊ทผ๋ฐ ์ญ์ ๋ฒํผ์ ๋๋ฌ๋ ๋ฆฌ์คํธ์์ ๋ฐ๋ก ์ ์ง์์ง๊ณ ๋กํ๋ ๋จ์์์ด์. F5 ๋๋ฌ์ ์ฉ์ผ๋ก ์๋ก๊ณ ์นจ ํด์ผ์ง๋ง ๋ธ๋ผ์ฐ์ ์์ ์ฌ๋ผ์ง๋ค๋๊น์? ์บ์๊ฐ ๋๋ฌด ๋
ํ ๊ฑฐ ์๋์์?
๐ฆ ์ํธ: ์๋ฒ(DB)์์ ๊ธ์ ์ง์ฐ๋ ํ์(Mutation)๋ ์ฑ๊ณตํ์ง๋ง, ์ ์ ์ฐ๋ฆฌ๊ฐ ๋ค๊ณ ์๋ ๋ก์ปฌ ์บ์(์๋นต) ์๊ฒ "์ผ! ์ ๊ธ ์ง์์ก์ผ๋๊น ๋ ๋ฐ์ดํฐ ๋ฒ๋ฆฌ๊ณ ์๋ฒ์์ ๋ค์ ์ค๋ ์ท ๋ฐ์!" ๋ผ๊ณ ๋ช ๋ น์ ์ ๋ด๋ ธ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๐ฃ ์์ฒ : ์ด... ์บ์๋ ์์์ ์ ๋ฐ์ดํธ๋๋ ๊ฑฐ ์๋์์ด์?
๐ฆ ์ํธ: "GET ์์ฒญ" ์บ์ฑ๋ง ์๋์ด์ฃ . ์ธ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์๊ณ (POST, DELETE, PUT) ๊ฐฑ์ ํ ์ง๋ ์ฐ๋ฆฌ๊ฐ ์ง์ ์๋ ค์ค์ผ ํด์. useMutation๊ณผ invalidateQueries ์ฝค๋ณด๋ฅผ ๋ชจ๋ฅด๋ฉด ํ์ ์ ์ ํํ
F5 ์๋ก๊ณ ์นจ๋ง ๊ฐ์ํ๊ฒ ๋ฉ๋๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ: ์บ์ ์๋ฒ ๋๊ธฐํ์ ๋ณธ์ง
ํ๋ก ํธ์๋์์ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์ ๋ณด๋ด์ ๋ณ๊ฒฝ(Create, Update, Delete)ํ๋ ํ์๋ฅผ ํตํ์ด Mutation(๋์ฐ๋ณ์ด, ๋ณํ) ์ด๋ผ๊ณ ๋ถ๋ฆ
๋๋ค.
์ฐ๋ฆฌ๋ ์์ useQuery๋ก ๋ฐ์ดํฐ๋ฅผ ์์ ํ ๊ฐ์ ธ์ค๋(Read) ๋ฒ๋ง ๋ฐฐ์ ์ต๋๋ค.
ํ์ง๋ง ์ปค๋ฎค๋ํฐ ์ฑ์์๋ ๊ธ์ ์ฐ๊ณ , ์ง์ฐ๊ณ , ์ข์์๋ฅผ ๋๋ฆ ๋๋ค. ์ด๋ฅผ ์ํด์ ํฌ๊ฒ ๋ ๋จ๊ณ ์ ์ก์ ์ด ํผ์ฒ๋ผ ์ด์ด์ ธ์ผ ํฉ๋๋ค.
- ์๋ฒ์ ๋ณ๊ฒฝ ์์ฒญ์ ๋ ๋ฆฐ๋ค. (
useMutation) - ๋ณ๊ฒฝ์ด ์๋ฃ๋์๋ง์, ๊ณผ๊ฑฐ์ ๋ถ๋ฌ์๋ ์ค๋๋ ๋ฆฌ์คํธ ๋ฐ์ดํฐ ์บ์๋ฅผ ์ฐ๋ ๊ธฐํต์ ์ฒ๋ฐ๊ณ ์ต์ ๋ชฉ๋ก์ ๋ค์ ๋ถ๋ฌ์ค๊ฒ๋ ๊ฐ์ ํ๋ค. (
invalidateQueries)
์ด ํ๋ฆ(Flow)์ ์์ ์์ฌ๋ก ๋ค๋ฃจ๋ ๊ฒ์ด 5๋ ์ฐจ ์ค๋ฌด์ ์์์ ์ ๋๋ค.
1. ์๋ฒ ํต์ ์ ๋ง๋ฒ์ฌ: useMutation
์์ฒ ์ด๊ฐ ์งฐ๋ ์ฝ๋๋ฅผ ๋์ง์ด ๋ด
์๋ค.
๋ณดํต ๋ฐ๋๋ผ JS์์๋ ๋ฒํผ ์จํด๋ฆญ ํธ๋ค๋ฌ ์์ ์ฉ fetch๋ axios๋ฅผ ์ค์
๋ฃ์ต๋๋ค.
// โ ์์ฒ ์ด์ ์์งํ ๋์ฐ๋ณ์ด
const handleDelete = async (id) => {
setIsLoading(true); // ์ํด, ๋น๊ธ๋น๊ธ ์คํผ๋ ๋์ฐ๊ณ
try {
await axios.delete(`/posts/${id}`);
alert('์ญ์ ์๋ฃ!');
// ์์ฒ : "๋ค ๋๋ฌ์ด... ๊ทผ๋ฐ ํ๋ฉด์ ์ด๋ป๊ฒ ๋ค์ ๊ทธ๋ฆฌ์ง? window.location.reload()?" ๐ฅ
} catch (error) {
alert('์ญ์ ์คํจ ใ
ใ
');
} finally {
setIsLoading(false);
}
}์ด๊ฑธ React Query์ useMutation ํ
์ผ๋ก ๊ฐ์ธ๋ฉด? ์์ฒญ๋๊ฒ ์ ์ธ์ ์ธ ์ฝ๋๋ก ๋ณ์ ํฉ๋๋ค.
// โ
์ํธ ๋ฆฌ๋์ ์ฐ์ํ Mutation ๋ถ๋ฆฌ
const deletePostMutation = useMutation({
mutationFn: (id: number) => axios.delete(`/posts/${id}`),
});
// ๋ฒํผ ํด๋ฆญ ํธ๋ค๋ฌ
const handleDelete = (id) => {
deletePostMutation.mutate(id);
};
// ๋ ๋๋ง ๋ทฐ (๋ก๋ฉ, ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ปดํฌ๋ํธ์ ๋
น์ฌ๋)
return (
<button
disabled={deletePostMutation.isPending}
onClick={() => handleDelete(1)}
>
{deletePostMutation.isPending ? '์ญ์ ์ค...' : '์ญ์ '}
</button>
);์ฐ๋ฆฌ๋ mutate๋ผ๋ ๋ฐฉ์์ (Trigger) ํจ์ ํ๋๋ง ๋ก๊ธฐ๋ฉด ๋ฉ๋๋ค. isPending, isError, isSuccess ๊ฐ์ ์ฌ์ด๋ ์ดํํธ ์ฒ๋ฆฌ๋ ํ
์ด ์์์ ๊ด๋ฆฌํด ์ฃผ์ฃ .
๐ก ์ฌ์ธต ํด๋ถ:
mutatevsmutateAsync์ ์ฐจ์ด
mutate: ๋ท์ผ์ ๋ฆฌ์กํธ ์ฟผ๋ฆฌ์ ๋งก๊ธฐ๊ณ ์ฝ๋ฐฑํจ์(onSuccess,onError) ์ชฝ์ ๋ก์ง์ ํ์ฐ๋ "Fire and Forget" ๋ฐฉ์. ๋๋ถ๋ถ์ ์ํฉ์์ ๊ถ์ฅ๋ฉ๋๋ค.mutateAsync: ์ง์ ํ๋ฉธ์ ์๋ฌ๋ฅผ ์ก์์ผ ํ๊ฑฐ๋, ์์ฐจ์ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ฅผ ๋ฌถ์ ๋(await ๋ถ์ฌ์) ๊ฐ์ ๋ก ์ฌ์ฉํ๋ ๋ฐฉ์. (TkDodo ์ํฐํด #12 ์ฐธ๊ณ )
2. ๋ถ์์ผ๋ฉด ์๋ก ๊ณ ์นจํ๋ผ: invalidateQueries
์, ๋ฐฉ๊ธ deletePostMutation์ด ๋ฌด์ฌํ ๋๋ฌ๋ค๊ณ ๊ฐ์ ํฉ์๋ค. DB์์๋ ๊ธ ๋ฒํธ 1๋ฒ์ด ์ง์์ก์ง๋ง, ๋ธ๋ผ์ฐ์ ํ๋ฉด(์ ๋จธ๋๋จผ ์บ์ ๊ธ๊ณ )์ ['posts'] ์ฟผ๋ฆฌ๋ ์์ง 1๋ฒ ๊ธ์ ๊ทธ๋ํ๊ฒ ํ๊ณ ์์ต๋๋ค.
์ด๋ onSuccess ์ฝ๋ฐฑ ์์์ ์ํธ ๋ฆฌ๋๊ฐ ๊บผ๋ด๋ ์ ๊ฐ์ ๋ณด๋, invalidateQueries ๊ฐ ๋ฑ์ฅํฉ๋๋ค.
// ์ ์ญ ์บ์ ์ ๊ทผ์ ์ํ ํด๋ผ์ด์ธํธ ๋๋นต ํธ์ถ
const queryClient = useQueryClient();
const deletePostMutation = useMutation({
mutationFn: (id) => axios.delete(`/posts/${id}`),
// ๐ ๋น๊ธฐ: ์ญ์ ๊ฐ '์ฑ๊ณต(Success)'ํ๋ฉด?
onSuccess: () => {
// ๐ก "์ผ! ['posts'] ๋ผ๋ ์ด๋ฆํ ๋ฌ๊ณ ์๋ ์บ์ ์น ๋ค ์ฐ๋ด๋๋ ๊ฑธ๋ก ๋ฌดํจํ(Invalidate)์์ผ!"
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});invalidateQueries ์ ๋์ ๋ฉ์ปค๋์ฆ์ ์์ ์ ์
๋๋ค.
['posts']ํค๋ฅผ ๊ฐ์ง ๋ชจ๋ ์ฟผ๋ฆฌ ์บ์๋ฅผ ์ฆ์ ์ํ(Stale) ์ํ๋ก ๊ฐ์ ๋ณ๊ฒฝ(๋งํน)ํฉ๋๋ค.- ๋ง์ฝ ํด๋น ๋ชฉ๋ก ์ปดํฌ๋ํธ(
useQuery(['posts']))๊ฐ ํ๋ฉด ์ด๋๊ฐ์ ๋ง์ดํธ(Active)๋์ด ๋์์ ธ ์๋ค๋ฉด? - "์ด๋ผ? ๋ ๋ฐฉ๊ธ ์ํ๋ค? ๋ค์ ์ต์ ๋ฐ์ดํฐ ๋ฐ์์ผ์ง!" ํ๊ณ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์นญ(Refetching)์ ์๋ ํญ๋ฐ ์ํต๋๋ค.
- ์ฌ์ฉ์๋ ๋ฒํผ ํด๋ฆญ ํ(์ฝ๊ฐ์ pending...), ์ฑ- ํ๊ณ ๋ฆฌ์คํธ์์ ์ญ์ ๋ ์ต์ ๋ ์ด์์์ ๋ง์ฃผํ๊ฒ ๋ฉ๋๋ค. ๋น์ฐํ ๋ธ๋ผ์ฐ์ ํฐ ํ๋ฉด(F5)์ ์ ํ ์์ต๋๋ค!
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์... ์ค๋ ์ ๋๋ก ํ๋ ํฐ๋ํ๋ค.
๋ด๊ฐ ์๋์ผ๋ก ๋ฐ์ดํฐ ๋ฐฐ์ด filter ๋๋ ค์ ์ญ์ ํ ๊ฐ์ฒด ์ง์์ฃผ๊ณ , ์๋ฌ ๋ฌ์ ๋ ๋กค๋ฐฑํ๊ณ ์์๋ฅผ ํ๋ ์ง๊ฑฐ๋ฆฌ๋ฅผ... ๊ทธ๋ฅ ์๋ฒ์์ ์ง์ ์ผ๋ ์บ์ํํ
"๊ฐ์ ๋ค ์์ด๋ฒ๋ฆฌ๊ณ ์ต์ ๋ฆฌ์คํธ ๋ค์ ๋ฐ์(invalidateQueries)" ๋ฑ ํ ๋ง๋ ์ํค๋๊น ๋ง๋ฒ์ฒ๋ผ ํ๋ฉด์ด ๊ฐฑ์ ๋๋ค.
๐ก ์ค๋์ ๊ตํ: "์๋ฒ ๊ฐ๊ตฌ๋ ๋ณํฅ์
useMutation์ผ๋ก ์ฐ์ํ๊ฒ ๋นผ๊ณ , ์ผ์ด ๋๋ ๋ค์ ๋ฌด์กฐ๊ฑดinvalidateQueries๋ก ์บ์ํต์ ๋ฐ๋ก ํ๋ฒ ๋ปฅ ์ฐจ์ ์๋ก๊ณ ์นจ์์ผ์ฃผ์!"
์ฌ์ค onSuccess ์์์ ์ธ ๊ฑฐ๋ฉด ๊ทธ๋ฅ ์ปดํฌ๋ํธ ์๋ก ๋ง์ดํธ์์ผ์ฃผ๋ฉด ์ ๋๋ ์์ผ๋ก ์๊ฐํ๋๋ฐ, ์๊ฐํด๋ณด๋ ๊ทธ๋ ๊ฒ ํ๋ฉด ์ฑ๊ธ ํ์ด์ง ์ฑ(SPA)์ ๋งค๋ ฅ(๋ฒ๋ฉ์ ์์ด ๋ถ๋๋ฌ์ด ์ ํ)์ด ์์ ํ ์ฌ๋ผ์ง๋ ๊ฑฐ๊ตฌ๋. ์ ๋ฌดํจํ... ๋ฌดํจํ ์๊ฒ ํ ํ ์น๊ณ ์์ํ๊ฒ ๋งฅ์ฃผ ๋ง์๋ฌ ๊ฐ๋ค! ๐ป
๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
Q. ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ์์ฒ ์ด๊ฐ ํน์ ๊ฒ์๊ธ์ ์ข์์(Like)๋ฅผ ๋๋ฅด๋ ๋์ฐ๋ณ์ด(Mutation)๋ฅผ ๋ ๋ ธ์ต๋๋ค. ์์ฒ ์ด๋ ๋ค์๊ณผ ๊ฐ์ด ์๊ฐํ์ต๋๋ค. "๊ฒ์๊ธ ํ๋์ ์ํ๋ง ์ด์ง ๋ฐ๋ ๊ฑฐ๋๊น ๊ตณ์ด ์๋ฒ์ ๋ ๋ค๋
์ฌ ํ์ ์์ด ํ๋ก ํธ์๋ ์ชฝ์์ ์ง์ queryCache๋ฅผ ๊ฑด๋๋ ค ํํธ ๊ฐ์๋ฅผ ์ฌ๋ ค๋ฒ๋ ค์ผ๊ฒ ๋ค!"
์ํธ์ ๋ฐ์๊ณผ ์กฐ์ธ์ผ๋ก ๊ฐ์ฅ ์ ์ ํ ๊ฒ์?
- A) "์ฒ์ฌ์ ๋๋ค, ์์ฒ ๋! ๋คํธ์ํฌ ํต์ ์ ์๋ผ๋ ค๋ฉด ํญ์ ๊ทธ๋ ๊ฒ ํ๋ก ํธ๋จ ๋ก์ปฌ ์บ์๋ฅผ ๋ฏ์ด๊ณ ์ณ์ผ ํฉ๋๋ค."
- B) "์๋์ค! ๋ง์ฝ ์ข์์ ์๋ต์ด ์ค๊ธฐ ์ง์ ์ ๋๊ตฐ๊ฐ๊ฐ ๊ธ ๋ด์ฉ์ ์์ ํ๋ค๋ฉด ์ด๋กํ๋์? ํ๋ก ํธ์๋ ์บ์๋ ์ ๋ ์๋ ์กฐ์ํ์ง ๋ง๊ณ , ๋ฌด์กฐ๊ฑด
invalidateQueries๋ก๋ง ๊ฐฑ์ ํด์ผ ๋๊ธฐํ๊ฐ ๊นจ์ง์ง ์์ต๋๋ค." - C) "
mutateAsync๋ฅผ ์ฌ์ฉํด์ ์๋์ผ๋ก ์ปดํฌ๋ํธ์ ๋ก์ปฌ ์ํ(useState)์ ๋ด์ ์ ๋ฐ์ดํธํด์ผ ํฉ๋๋ค."
โ
์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: TkDodo ์ํฐํด #10์ ๋ฐ๋ฅด๋ฉด, QueryCache๋ ๋น์ ์ ๊ฐ์ธ ๋ก์ปฌ ์ํ ๋ณ์ํ๊ฐ ์๋๋ผ 'ํญ์ ์๋ฒ์ ์ต์ ์ค๋
์ท์ ๋์ค๊ธฐ ์ํ ๊ฑฐ์ธ'์ด์ด์ผ ํฉ๋๋ค. ๊ฐ๋ฐ์๊ฐ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์์ ํด๋ฒ๋ฆฌ๋ ๊ผผ์(
setQueryData๊ฐ์ ์กฐ์ ๋ฑ)๋ฅผ ๋จ๋ฐํ๋ฉด, ํฅํ ๋ณต์กํ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์นญ ์ฌ์ดํด๊ณผ ์์ผ์ ๋๋ฒ๊น ์ด ์ฃฝ์ด๋๋(Race Condition ํญ๋ฐ) ์คํ๊ฒํฐ ์์คํ ์ด ๋ฉ๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, ๋คํธ์ํฌ ์ฝ ํ๋ฒ ๋ ์๋ ๊ฒ ์๊น์์ ์บ์๊ฐ์ ์ง์ ๋ฐ๊พธ์๋ฉด ์ ๋ฉ๋๋ค! ๊ทธ๊ฑด ๋์ค์ ๋ฐฐ์ธ ์ง์ ํ ๋๊ด์ ์
๋ฐ์ดํธ(Optimistic Update) ์์ ์ ํ์ ์ผ๋ก ์ฐ๋ ๊ณ ์ค์ค๊ธ ๋น๊ธฐ๊ณ ์, ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ์ ์ฟจํ๊ฒ
invalidateQueries๋ฑ ๋๋ ค์ ์๋ฒ์ ์ง์ง ๊ฒฐ๊ณผ๋ฌผ๋ก ๋ฎ์ด์์์ผ ์์ ํฉ๋๋ค." - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ์ฃ๋ถ๋ฅธ ์บ์ ์ง์ ์กฐ์ ๊ธ์ง! ๋ถ์์ผ๋ฉด ๋ฌดํจํ ๋น(
invalidate) ๋ฐ์ฌ!