ํ์คํ ์ฟผ๋ฆฌ์์ ๋๊ด์ ์ ๋ฐ์ดํธ๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ฃผ๋๋ฐ, ์ด๊ฒ ์ ๋ง ์ ์ฉํ๋ค. ๊ฐ๋จํ ๋งํ๋ฉด ์๋ฒ์์ ์๋ต์ด ์ค๊ธฐ ์ ์ ๋ฏธ๋ฆฌ UI๋ฅผ ์ ๋ฐ์ดํธํด๋ฒ๋ฆฌ๋ ๊ฑฐ๋ค. ์๋ฒ์์ ๊ฐ์ ๋ฐ์์ค๋ ๋๋ ์ด๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ฐ๋ก ํ๋ฉด์ ๋ฐ์ํด์ฃผ๋ ๊ฒ.
์๋ฅผ ๋ค์ด ์ธ์คํ๊ทธ๋จ์์ ์ข์์ ๋ฒํผ์ ๋๋ ๋ค๊ณ ์๊ฐํด๋ณด์. ์๋ฒ ์๋ต์ด 2์ด ์ ๋ ๊ฑธ๋ฆฐ๋ค๋ฉด, ์ฌ์ฉ์๋ ์ข์์๋ฅผ ๋๋ ๋๋ฐ๋ ํํธ๊ฐ ๋ฐ๋ก ๋นจ๊ฐ์์ผ๋ก ์ ๋ณํ๊ณ 2์ด ๋์ ๊ธฐ๋ค๋ ค์ผ ํ๋ค. ์์ฒญ ๋ต๋ตํ ๊ฑฐ๋ค.
๋๊ด์ ์ ๋ฐ์ดํธ๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ค๋ค. ์ข์์ ๋ฒํผ์ ๋๋ฅด๋ฉด ์๋ฒ ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ์ฆ์ ํํธ๋ฅผ ๋นจ๊ฐ์์ผ๋ก ๋ฐ๊ฟ์ค๋ค. ๊ทธ๋ผ ์ฌ์ฉ์๋ "์ค! ์ ๋๋ ธ๋ค!" ํ๊ณ ๋ฐ๋ก ์ ์ ์๋ค.
๋ฌผ๋ก ์ด๋ ๊ฒ ๋ฏธ๋ฆฌ UI๋ฅผ ๋ฐ๊ฟ๋จ๋๋ฐ ์๋ฒ์์ ์๋ฌ๊ฐ ๋๋ฉด? ๊ทธ๋ ๋ค์ ์๋๋๋ก ๋๋ ค๋์ผ ํ๋ค. ์ด๊ฑธ ๋กค๋ฐฑ์ด๋ผ๊ณ ํ๋๋ฐ, ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ์ธ ๋๋ ์ด ๋กค๋ฐฑ ๋ก์ง๋ ํจ๊ป ๊ตฌํํด์ค์ผ ํ๋ค.
ํ์คํ ์ฟผ๋ฆฌ์์๋ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค:
์ด๊ฑด ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด๋ค. ํน๋ณํ ์ฝ๋ ์์ด ๊ทธ๋ฅ ์ปดํฌ๋ํธ์์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์ฌ์ฉํ๋ค.
const addTodoMutation = useMutation({
mutationFn: (newTodo) => axios.post("/api/todos", { text: newTodo }),
onSettled: async () => {
return await queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
// ์ด๋ ๊ฒ ์ํ๋ฅผ ๊ฐ์ ธ์์
const { isPending, variables, mutate, isError } = addTodoMutation;
// JSX์์ ์ด๋ ๊ฒ ์ฌ์ฉ
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
{/* ์์ฒญ ์ค์ผ ๋ ์์๋ก ๋ณด์ฌ์ฃผ๊ธฐ */}
{isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
{/* ์๋ฌ ๋ฌ์ ๋ */}
{isError && (
<li style={{ color: "red" }}>
{variables}
<button onClick={() => mutate(variables)}>๋ค์ ์๋</button>
</li>
)}
</ul>
์์ฒญ์ด ์งํ ์ค์ผ ๋(isPending์ด true์ผ ๋) ์์ ํญ๋ชฉ์ ํ๋ฆฟํ๊ฒ ๋ณด์ฌ์ฃผ๊ณ , ์๋ฌ๊ฐ ๋๋ฉด ๋นจ๊ฐ์์ผ๋ก ํ์ํ๋ฉด์ ๋ค์ ์๋ ๋ฒํผ์ ๋ณด์ฌ์ฃผ๋ ์์ด๋ค.
์ด ๋ฐฉ๋ฒ์ ์กฐ๊ธ ๋ ๋ณต์กํ๋ฐ, onMutate ํจ์๋ฅผ ์ฌ์ฉํ๋ค. onMutate๋ API๊ฐ ํธ์ถ๋๊ธฐ ์ ์ ์คํ๋๋ ํจ์๋ค. ์ฌ๊ธฐ์ ์บ์๋ฅผ ๋ฏธ๋ฆฌ ์
๋ฐ์ดํธํด๋๊ณ , ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ๋กค๋ฐฑํ ์ ์๊ฒ ์ค๋นํ๋ค.
const queryClient = useQueryClient();
useMutation({
mutationFn: addTodo,
// ์ฌ๊ธฐ๊ฐ ํต์ฌ! API ํธ์ถ ์ ์ ์คํ๋จ
onMutate: async (newTodo) => {
// ๋ค๋ฅธ ์ฟผ๋ฆฌ ์์ฒญ ๋ค ์ทจ์์์ผ์ ๊ผฌ์ด์ง ์๊ฒ
await queryClient.cancelQueries({ queryKey: ["todos"] });
// ํ์ฌ ์ํ ๋ฐฑ์
ํด๋๊ธฐ (๋กค๋ฐฑ์ฉ)
const previousTodos = queryClient.getQueryData(["todos"]);
// ์บ์ ๋ฐ๋ก ์
๋ฐ์ดํธ
queryClient.setQueryData(["todos"], (old) => [...old, newTodo]);
// ๋ฐฑ์
ํ ๋ฐ์ดํฐ ๋ฐํ
return { previousTodos };
},
// ์๋ฌ ๋๋ฉด ์ด์ ์ํ๋ก ๋ณต๊ตฌ
onError: (err, newTodo, context) => {
queryClient.setQueryData(["todos"], context.previousTodos);
},
// ์์ฒญ ๋๋๋ฉด ๋ฌด์กฐ๊ฑด ์๋ก๊ณ ์นจ
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
๋ ๋ฐฉ๋ฒ ์ค์ ๋ญ ์ ํํ ์ง๋ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๋ค:
UI ๋ฐฉ์์ด ์ข์ ๋:
์บ์ ๋ฐฉ์์ด ์ข์ ๋:
์๋ฌด ๋๋ ์ฐ์ง ๋ง์: ์ค์ํ ๋ฐ์ดํฐ๋ ์คํจ ํ๋ฅ ์ด ๋์ ์์ ์ ๋๊ด์ ์ ๋ฐ์ดํธ ์ ์ฐ๋ ๊ฒ ์ข๋ค.
ํญ์ ๋กค๋ฐฑ ์ค๋นํ๊ธฐ: ์๋ฒ์์ ์๋ฌ๊ฐ ๋๋ฉด ์ด์ ์ํ๋ก ๋๋๋ฆด ์ ์์ด์ผ ํ๋ค.
์๋ฌ ๋ฌ์ ๋ ์ฌ์ฉ์์๊ฒ ์๋ ค์ฃผ๊ธฐ: ์ ๋ฐ์ดํธ ์คํจํ์ ๋ ์ฌ์ฉ์๊ฐ ์ ์ ์๊ฒ ํด์ฃผ์.
๋๊ด์ ์ ๋ฐ์ดํธ๋ ์๋ฒ ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ UI๋ฅผ ๋จผ์ ์ ๋ฐ์ดํธํด์ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ๊ธฐ๋ฒ์ด๋ค. ํ์คํ ์ฟผ๋ฆฌ์์๋ UI ๋ฐฉ์๊ณผ ์บ์ ๋ฐฉ์ ๋ ๊ฐ์ง๋ก ๊ตฌํํ ์ ์๋๋ฐ, ์ํฉ์ ๋ง๊ฒ ์ ํํด์ ์ฐ๋ฉด ๋๋ค. ๋ค๋ง ๋กค๋ฐฑ ๋ก์ง์ ๊ผญ ๊ตฌํํด์ผ ํ๊ณ , ๋ชจ๋ ์ํฉ์ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ์ ์ฉํ๋ ๊ฑด ์ํํ ์ ์์ผ๋ ์ ์ ํ ์ฌ์ฉํ์.