๐Ÿ“– TIL - TanStack Query์˜ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ๊ธฐ๋ฒ• ์ดํ•ดํ•˜๊ธฐ

์Š˜ยท2025๋…„ 3์›” 10์ผ

๐Ÿ“– TIL

๋ชฉ๋ก ๋ณด๊ธฐ
72/90

๐Ÿ“Œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ญ์•ผ?

ํƒ„์Šคํƒ ์ฟผ๋ฆฌ์—์„œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์ฃผ๋Š”๋ฐ, ์ด๊ฒŒ ์ •๋ง ์œ ์šฉํ•˜๋‹ค. ๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ์‘๋‹ต์ด ์˜ค๊ธฐ ์ „์— ๋ฏธ๋ฆฌ UI๋ฅผ ์—…๋ฐ์ดํŠธํ•ด๋ฒ„๋ฆฌ๋Š” ๊ฑฐ๋‹ค. ์„œ๋ฒ„์—์„œ ๊ฐ’์„ ๋ฐ›์•„์˜ค๋Š” ๋”œ๋ ˆ์ด๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ํ™”๋ฉด์— ๋ฐ˜์˜ํ•ด์ฃผ๋Š” ๊ฒƒ.

๐Ÿค” ์™œ ์ด๋Ÿฐ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ• ๊นŒ?

์˜ˆ๋ฅผ ๋“ค์–ด ์ธ์Šคํƒ€๊ทธ๋žจ์—์„œ ์ข‹์•„์š” ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. ์„œ๋ฒ„ ์‘๋‹ต์ด 2์ดˆ ์ •๋„ ๊ฑธ๋ฆฐ๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๋Š” ์ข‹์•„์š”๋ฅผ ๋ˆŒ๋ €๋Š”๋ฐ๋„ ํ•˜ํŠธ๊ฐ€ ๋ฐ”๋กœ ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ์•ˆ ๋ณ€ํ•˜๊ณ  2์ดˆ ๋™์•ˆ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค. ์—„์ฒญ ๋‹ต๋‹ตํ•  ๊ฑฐ๋‹ค.

๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด์ค€๋‹ค. ์ข‹์•„์š” ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์„œ๋ฒ„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์ฆ‰์‹œ ํ•˜ํŠธ๋ฅผ ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค. ๊ทธ๋Ÿผ ์‚ฌ์šฉ์ž๋Š” "์˜ค! ์ž˜ ๋ˆŒ๋ ธ๋„ค!" ํ•˜๊ณ  ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋ฌผ๋ก  ์ด๋ ‡๊ฒŒ ๋ฏธ๋ฆฌ UI๋ฅผ ๋ฐ”๊ฟ”๋†จ๋Š”๋ฐ ์„œ๋ฒ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด? ๊ทธ๋• ๋‹ค์‹œ ์›๋ž˜๋Œ€๋กœ ๋Œ๋ ค๋†”์•ผ ํ•œ๋‹ค. ์ด๊ฑธ ๋กค๋ฐฑ์ด๋ผ๊ณ  ํ•˜๋Š”๋ฐ, ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ์“ธ ๋•Œ๋Š” ์ด ๋กค๋ฐฑ ๋กœ์ง๋„ ํ•จ๊ป˜ ๊ตฌํ˜„ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

๐Ÿ’ก TanStack Query์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜์ง€?

ํƒ„์Šคํƒ ์ฟผ๋ฆฌ์—์„œ๋Š” ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค:

1. 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์ผ ๋•Œ) ์ž„์‹œ ํ•ญ๋ชฉ์„ ํ๋ฆฟํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ๊ณ , ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ํ‘œ์‹œํ•˜๋ฉด์„œ ๋‹ค์‹œ ์‹œ๋„ ๋ฒ„ํŠผ์„ ๋ณด์—ฌ์ฃผ๋Š” ์‹์ด๋‹ค.

2. ์บ์‹œ๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•˜๊ธฐ

์ด ๋ฐฉ๋ฒ•์€ ์กฐ๊ธˆ ๋” ๋ณต์žกํ•œ๋ฐ, 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 ๋ฐฉ์‹์ด ์ข‹์„ ๋•Œ:

  • ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์ด ํ•œ ์ปดํฌ๋„ŒํŠธ๋ฟ์ผ ๋•Œ
  • ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์„ ๋•Œ
  • ๋ณต์žกํ•œ ๋กค๋ฐฑ ๋กœ์ง์ด ํ•„์š” ์—†์„ ๋•Œ

์บ์‹œ ๋ฐฉ์‹์ด ์ข‹์„ ๋•Œ:

  • ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค„ ๋•Œ
  • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•  ๋•Œ
  • ํ™•์‹คํ•œ ๋กค๋ฐฑ์ด ์ค‘์š”ํ•  ๋•Œ

๐Ÿ’ช ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•  ์ 

  1. ์•„๋ฌด ๋•Œ๋‚˜ ์“ฐ์ง€ ๋ง์ž: ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋‚˜ ์‹คํŒจ ํ™•๋ฅ ์ด ๋†’์€ ์ž‘์—…์—” ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ์•ˆ ์“ฐ๋Š” ๊ฒŒ ์ข‹๋‹ค.

  2. ํ•ญ์ƒ ๋กค๋ฐฑ ์ค€๋น„ํ•˜๊ธฐ: ์„œ๋ฒ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด ์ด์ „ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

  3. ์—๋Ÿฌ ๋‚ฌ์„ ๋•Œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ ค์ฃผ๊ธฐ: ์—…๋ฐ์ดํŠธ ์‹คํŒจํ–ˆ์„ ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์ž.

๐Ÿ‘ ์š”์•ฝ

๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋Š” ์„œ๋ฒ„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  UI๋ฅผ ๋จผ์ € ์—…๋ฐ์ดํŠธํ•ด์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๋Š” ๊ธฐ๋ฒ•์ด๋‹ค. ํƒ„์Šคํƒ ์ฟผ๋ฆฌ์—์„œ๋Š” UI ๋ฐฉ์‹๊ณผ ์บ์‹œ ๋ฐฉ์‹ ๋‘ ๊ฐ€์ง€๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•ด์„œ ์“ฐ๋ฉด ๋œ๋‹ค. ๋‹ค๋งŒ ๋กค๋ฐฑ ๋กœ์ง์€ ๊ผญ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๊ณ , ๋ชจ๋“  ์ƒํ™ฉ์— ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฑด ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•˜์ž.

profile
์ฃผ๋‹ˆ์–ด ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ์žฅ๊ธฐ ๊ธฐ๋ก๊ธฐ๋ก

0๊ฐœ์˜ ๋Œ“๊ธ€