๐Ÿ’ก 06. Optimistic Updates (๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ) ์‹ค์ „

2026๋…„ 3์›” 4์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

์„œ๋ฒ„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  UI๋ฅผ ๋จผ์ € ์—…๋ฐ์ดํŠธํ•˜์—ฌ ์•ฑ์˜ ์ฒด๊ฐ ์†๋„(UX)๋ฅผ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ๊ธฐ๋ฒ•๊ณผ ์—๋Ÿฌ ๋กค๋ฐฑ ๋งค์ปค๋‹ˆ์ฆ˜์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ

"์˜์ˆ™(UX ๋””์ž์ด๋„ˆ) ๋‹˜, ์‚ฌ์šฉ์ž๊ฐ€ ์ข‹์•„์š” ๋ฒ„ํŠผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค 0.5์ดˆ์”ฉ ๋ ‰ ๊ฑธ๋ฆฌ๋˜ ๊ฑฐ ๋ฐฉ๊ธˆ ๊ณ ์ณค์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋ฒˆ๊ฐœ์ฒ˜๋Ÿผ ๋ณ€ํ•  ๊ฑฐ์˜ˆ์š”."

โ˜•๏ธ ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ: "์„œ๋ฒ„๊ฐ€ ๋А๋ฆฌ๋ฉด ํ™”๋ฉด๋„ ๋А๋ ค์•ผ ํ•˜๋‚˜์š”?"

(์•„์นจ ํšŒ์˜ ์‹œ๊ฐ„, ์˜์ˆ™ ๋””์ž์ด๋„ˆ์˜ ํ”ผ๋“œ๋ฐฑ์„ ๋“ฃ๋Š” ์˜์ฒ )

๐ŸŽจ ์˜์ˆ™ (UX ๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜, ์šฐ๋ฆฌ ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ ๋ง์ธ๋ฐ์š”... ์ธ์Šคํƒ€๊ทธ๋žจ์ด๋‚˜ ํŠธ์œ„ํ„ฐ๋Š” '์ข‹์•„์š”' ๋ˆ„๋ฅด๋ฉด ๋ฐ”๋กœ ๋นจ๊ฐ›๊ฒŒ ๋ณ€ํ•˜์ž–์•„์š”? ๊ทผ๋ฐ ์šฐ๋ฆฌ ์•ฑ์€ ๋ˆ„๋ฅด๊ณ  ๋‚˜์„œ ํ•œ 0.5์ดˆ ๋’ค์—์•ผ ์ƒ‰๊น”์ด ๋ณ€ํ•ด์„œ ๋˜๊ฒŒ ๋‹ต๋‹ตํ•ด์š”. ๊ฐ€๋”์€ ๋‚ด๊ฐ€ ์•ˆ ๋ˆŒ๋ €๋‚˜ ์‹ถ์–ด์„œ ๋˜ ๋ˆ„๋ฅด๊ฒŒ ๋ผ์š”."

๐Ÿฃ ์˜์ฒ : ์•„... ๊ทธ๊ฒŒ, ์ €ํฌ ์„œ๋ฒ„ ์‘๋‹ต ์†๋„๋ž‘ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ(Latency) ๋•Œ๋ฌธ์— ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์–ด์ฉ” ์ˆ˜๊ฐ€ ์—†๋Š” ๋ถ€๋ถ„์ด์—์š”. ๋ฐฑ์—”๋“œ์—์„œ 200 OK ์‚ฌ์ธ์ด ๋–จ์–ด์ ธ์•ผ ์ œ๊ฐ€ ๋ง˜ ๋†“๊ณ  ํ™”๋ฉด ์ƒ‰๊น”์„ ๋ฐ”๊ฟ”์ค„ ์ˆ˜ ์žˆ๋‹ค๊ณ ์š” ใ… ใ… 

๐Ÿฆ ์˜ํ˜ธ: ์˜์ฒ  ๋‹˜, ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ๋ฐฑ์—”๋“œ ์†๋„์— ํ•‘๊ณ„๋ฅผ ๋Œ€๋ฉด ์•ˆ ๋˜์ฃ . ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์š”์ฒญ(Mutation)์ด ๋‹น์—ฐํžˆ ์„ฑ๊ณตํ•  ๊ฒƒ์ด๋ผ ๋‚™๊ด€(Optimistic) ํ•˜๊ณ  ์ผ๋‹จ ํ™”๋ฉด๋ถ€ํ„ฐ ๋ƒ…๋‹ค ๋ฐ”๊ฟ”์ฃผ๋Š” Optimistic Updates ๊ธฐ๋ฒ•์„ ์“ฐ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: UX์˜ ์งˆ์  ๋„์•ฝ

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

React Query์—์„œ ์˜คํ”„๋ผ์ธ ํ™˜๊ฒฝ ๋ฐ ์—ด์•…ํ•œ ์ธํ„ฐ๋„ท ํ™˜๊ฒฝ์—์„œ๋„ ์™„๋ฒฝํžˆ ๋งค๋„๋Ÿฌ์šด UX๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ํ•„์ˆ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ์ˆ ์ด ๋ฐ”๋กœ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฌดํ„ฑ๋Œ€๊ณ  ํ™”๋ฉด๋งŒ ๋ฐ”๊ฟจ๋‹ค๊ฐ€ ์ง„์งœ๋กœ ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜(500 Error)๊ฐ€ ๋‚˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์ž˜๋ชป ๋ฐ”๋€ ํ™”๋ฉด์„ ์›๋ž˜๋Œ€๋กœ ๋˜๋Œ๋ฆฌ๋Š” ๋กค๋ฐฑ(Rollback) ์ž‘์—…์ด ์ •๊ตํ•˜๊ฒŒ ๋™๋ฐ˜๋˜์–ด์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ฑ•ํ„ฐ์—์„œ๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ์— ๋Œ€์ฒ˜ํ•˜๋Š” ์šฐ์•„ํ•œ ๋กค๋ฐฑ ๋กœ์ง์„ TypeScript์™€ ํ•จ๊ป˜ ์„ค๊ณ„ํ•ด๋ด…๋‹ˆ๋‹ค.


1. ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ์˜ ํ•„์ˆ˜ ์ฝœ๋ฐฑ 3์ด์‚ฌ

useMutation ํ›… ์•ˆ์—๋Š” ๋‚™๊ด€์  ์ƒํƒœ๊ณ„๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” 3๊ฐ€์ง€ ์ƒ๋ช…์ฃผ๊ธฐ(Lifecycle) ์ฝœ๋ฐฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  1. onMutate: ๋Œ์—ฐ๋ณ€์ด ์‹คํ–‰ ์ง์ „์— ๋ฐœ๋™! (์—ฌ๊ธฐ์„œ ํ™”๋ฉด์„ ๊ฐ€์งœ๋กœ ๋ฐ”๊พผ๋‹ค)
  2. onError: ์ง„์งœ๋กœ ์„œ๋ฒ„ ํ†ต์‹ ํ–ˆ๋Š”๋ฐ ์—๋Ÿฌ ๊ฐ€ ๋‚ฌ์„ ๋•Œ! (์—ฌ๊ธฐ์„œ ํ™”๋ฉด์„ ๋‹ค์‹œ ์˜›๋‚ ๋กœ ๋˜๋Œ๋ฆฐ๋‹ค)
  3. onSettled: ์„ฑ๊ณตํ•˜๋“  ์‹คํŒจํ•˜๋“  ๋งˆ์ง€๋ง‰ ์— ๋ฌด์กฐ๊ฑด ์‹คํ–‰! (์–ด์จŒ๋“  ๋๋‚ฌ์œผ๋‹ˆ ์„œ๋ฒ„ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋กœ ์ตœ์ข… ๋ฎ์–ด์”Œ์šด๋‹ค)

๐Ÿ“Œ TypeScript ํƒ€์ž…์˜ ์ค‘์š”์„ฑ

TypeScript ํ™˜๊ฒฝ์—์„œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ์งค ๋•Œ๋Š” ์ œ๋„ค๋ฆญ(Generic) ํƒ€์ž…์„ ์ •ํ™•ํžˆ ๋งž์ถ”๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ onError๋กœ ์˜›๋‚  ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ๋ ค๋ฉด(Context), onMutate์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ํƒ€์ž…๊ณผ onError์—์„œ ๋ฐ›๋Š” Context์˜ ํƒ€์ž…์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿ“ Mutation Context ํƒ€์ž… ํ‘œ๊ธฐ
// ์ด์ „ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด๋’€๋‹ค๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด ๋กค๋ฐฑํ•  ๋•Œ ์“ฐ๋Š” '์•ˆ์ „ ์žฅ์น˜'
type TodoContext = {
  previousTodos: Todo[] | undefined;
};

2. ์‹ค์ „ ์ฝ”๋“œ ์›Œํฌ์ƒต: ์ข‹์•„์š” ๊ธฐ๋Šฅ ์™„๋ฒฝ ๊ตฌํ˜„

์˜์กฐ ๋ฆฌ๋“œ๊ฐ€ ์ง์ ‘ ์งœ์ฃผ๋Š” "์ ˆ๋Œ€ ์‹คํŒจํ•˜์ง€ ์•Š๋Š” ๋‚™๊ด€์  ์ข‹์•„์š” ๋ฒ„ํŠผ" ์ฝ”๋“œ๋ฅผ ํ•œ ์ค„์”ฉ ๋œฏ์–ด๋ด…์‹œ๋‹ค.

import { useMutation, useQueryClient } from '@tanstack/react-query';
 
// DB์— ์ €์žฅ๋œ Post ํƒ€์ž…
type Post = {
  id: number;
  title: string;
  likes: number; // ์ข‹์•„์š” ๊ฐœ์ˆ˜
};
 
export function useLikePost() {
  const queryClient = useQueryClient();
 
  return useMutation({
    // 1๏ธโƒฃ ์ง„์งœ ์„œ๋ฒ„ ํ†ต์‹ 
    mutationFn: async (postId: number) => {
      // (๊ฐ€์ •) ์‹œ๊ฐ„์ด 1์ดˆ ๊ฑธ๋ฆฌ๋Š” ๋А๋ฆฐ API
      const res = await axios.post(`/posts/${postId}/like`);
      return res.data;
    },
 
    // 2๏ธโƒฃ ๐Ÿš€ [ํ•ต์‹ฌ] onMutate: ๋ฒ„ํŠผ ํด๋ฆญ ์ฆ‰์‹œ ๋ฐœ๋™! (์„œ๋ฒ„ ๋Œ€๊ธฐ ์•ˆ ํ•จ)
    onMutate: async (postId: number) => {
      // 1. ํ˜น์‹œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ ํŽ˜์น˜๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฐ•์ œ๋กœ ์ทจ์†Œ! 
      // (์ด ๊ฐ’์ด ์ด์ „ ๊ฒฐ๊ณผ๋กœ ๋ฎ์–ด์”Œ์›Œ์ง€๋Š” ์ตœ์•…์˜ Race Condition ๋ฐฉ์ง€)
      await queryClient.cancelQueries({ queryKey: ['posts'] });
 
      // 2. ์—๋Ÿฌ ๋‚ฌ์„ ๋•Œ๋ฅผ ๋Œ€๋น„ํ•ด์„œ, ์—…๋ฐ์ดํŠธ ์ „ '์›๋ž˜ ๋ฐ์ดํ„ฐ'๋ฅผ ๋ณ€์ˆ˜์— ์ž ๊น ๋ณด๊ด€ (์Šค๋ƒ…์ƒท)
      const previousPosts = queryClient.getQueryData<Post[]>(['posts']);
 
      // 3. โœจ ๋“œ๋””์–ด ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ๋ฐœ๋™! ํ™”๋ฉด์— ๋ณด์ผ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ˆ˜์ •ํ•œ๋‹ค.
      queryClient.setQueryData<Post[]>(['posts'], (old) => {
        if (!old) return [];
        return old.map(post => 
          // ๋‚ด๊ฐ€ ์ง€๊ธˆ ๋ˆ„๋ฅธ ๊ฒŒ์‹œ๋ฌผ์˜ ์ข‹์•„์š” ์ˆ˜๋งŒ ๊ฐ€์งœ๋กœ +1 ์‹œ์ผœ๋ฒ„๋ฆผ (ํ™”๋ฉด ์ฆ‰์‹œ ๋ฐ˜์˜)
          post.id === postId ? { ...post, likes: post.likes + 1 } : post
        );
      });
 
      // 4. ์›๋ž˜ ๋ณด๊ด€ํ•ด๋‘” ๋ฐ์ดํ„ฐ๋ฅผ Context(ํ”ผ๋‚œ์ฒ˜)์— ๋‹ด์•„์„œ ๋ฆฌํ„ด! -> ์ด๊ฑด onError๋กœ ์ „๋‹ฌ๋จ
      return { previousPosts };
    },
 
    // 3๏ธโƒฃ ๐Ÿ’ฃ ๋งŒ์•ฝ ์„œ๋ฒ„์—์„œ 500 ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋‹ค๋ฉด? (๋กค๋ฐฑ)
    onError: (err, postId, context) => {
      // context์— ์•„๊นŒ onMutate์—์„œ ๋ฆฌํ„ดํ–ˆ๋˜ ์›๋ž˜ ์Šค๋ƒ…์ƒท์ด ๋“ค์–ด์žˆ๋‹ค.
      if (context?.previousPosts) {
        // ์›๋ž˜ ๋ฐ์ดํ„ฐ๋กœ ๋‹ค์‹œ ์บ์‹œ๋ฅผ ๋ณต๊ตฌ์‹œ์ผœ๋ฒ„๋ฆฐ๋‹ค! (Undo)
        queryClient.setQueryData(['posts'], context.previousPosts);
      }
    },
 
    // 4๏ธโƒฃ โœ… ์„ฑ๊ณต์ด๋“  ์‹คํŒจ๋“  ๋ชจ๋“  ๊ฒŒ ๋๋‚ฌ๋‹ค๋ฉด? (๊ฒ€์ฆ)
    onSettled: () => {
      // ํ˜น์‹œ ๋กœ์ปฌ ์กฐ์ž‘ ์ค‘ ๊ผฌ์˜€์„์ง€ ๋ชจ๋ฅด๋‹ˆ ์„œ๋ฒ„์—์„œ ์ฐ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊น”๋”ํ•˜๊ฒŒ ๋ถˆ์–ด์˜จ๋‹ค.
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });
}

์ด ํ๋ฆ„์€ ๊ฑฐ์˜ ๊ณต์‹(Boilerplate)์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ์™ธ์žฅํ•˜๋“œ์ฒ˜๋Ÿผ ๋–ผ๋‹ค ๋ถ™์ด์…”๋„ ๋  ์ •๋„๋กœ ์™„๋ฒฝํ•œ ์•ˆ์ •์„ฑ์„ ์ž๋ž‘ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์™€, ์†”์งํžˆ ์ฝ”๋“œ ๋ณผ ๋• onMutate, cancelQueries, setQueryData ์ฃ„๋‹ค ์„ž์—ฌ ์žˆ์–ด์„œ ๋ณต์žกํ•ด ๋ณด์˜€๋Š”๋ฐ ์ง์ ‘ ํƒ€์ž ์ณ๋ณด๋‹ˆ๊นŒ ์›๋ฆฌ๊ฐ€ ๊ธฐ๊ฐ€ ๋ง‰ํžŒ๋‹ค.
"1๋ฒˆ ๋ฉˆ์ถ”๊ณ  -> 2๋ฒˆ ๊ณผ๊ฑฐ ๋ฐฑ์—…ํ•˜๊ณ  -> 3๋ฒˆ ์ตœ์‹ ์ธ ์ฒ™ ์œ„์žฅํ•˜๊ณ  -> 4๋ฒˆ ๋งํ•˜๋ฉด ๋ณต๊ตฌ / ์„ฑ๊ณตํ•˜๋ฉด ๋ฆฌ๋‰ด์–ผ". ์ด ๋„ค ๋ฐ•์ž๊ฐ€ ์™„์ „ ์˜ˆ์ˆ ์ด๋„ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์„œ๋ฒ„๊ฐ€ ๋А๋ฆฌ๋‹ค๊ณ  ๋‚ด ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ๋А๋ฆด ํ•„์š”๋Š” ์—†๋‹ค! ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋ฏฟ๊ณ  (Optimistic) ํ™”๋ฉด๋ถ€ํ„ฐ ๋ณด์—ฌ์ค€ ๋’ค, ๋’ท๋‹จ์—์„œ ์‚ฌ๊ณ  ๋‚˜๋ฉด ์กฐ์šฉํžˆ ๋กค๋ฐฑ(Rollback)ํ•ด์ฃผ๋Š” ์šฐ์•„ํ•œ ์‚ฌ๊ธฐ(?)๋ฅผ ์น˜์ž."

์˜์ˆ™ ๋””์ž์ด๋„ˆ ๋‹˜ํ•œํ…Œ ์ด๊ฑฐ ์ ์šฉํ•˜๊ณ  ์‹œ์—ฐ ๋ณด์—ฌ๋“œ๋ ธ๋”๋‹ˆ ๋ˆˆ์ด ๋˜ฅ๊ทธ๋ž˜์ ธ์„  ์ปคํ”ผ ์‚ฌ์ฃผ์…จ๋‹ค ใ…‹ใ…‹ใ…‹. ๋‚ด์ผ์€ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์•„์ดํ…œ ๋‹ด๋Š” ๊ฒƒ๋„ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋กœ ๋ฐ”๊ฟ”๋†”์•ผ์ง€. (๋‹จ, ๊ฒฐ์ œ ๊ฐ™์€ ์น˜๋ช…์ ์ธ ๊ฑด ๋‚™๊ด€์ ์œผ๋กœ ํ•˜๋ฉด ํฐ์ผ๋‚œ๋‹ค๊ณ  ์˜ํ˜ธ ๋‹˜์ด ๊ฒฝ๊ณ ํ•˜์…จ๋‹ค... ํ†ต์žฅ ์ž”๊ณ  0์›์ธ๋ฐ ๊ฒฐ์ œ ์™„๋ฃŒ ๋„์šธ ๋ป” ใ…‹)


๐Ÿ“ ๋ฐฐ์šด ๋‚ด์šฉ ์ ๊ฒ€ํ•˜๊ธฐ (Quiz)

Q. ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” onMutate ๋ธ”๋ก์˜ ์ตœ์ƒ๋‹จ์—์„œ ๊ฐ€์žฅ ๋จผ์ € await queryClient.cancelQueries({ queryKey: [...] }) ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ™œ์„ฑํ™”๋œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ทจ์†Œ(Cancel)ํ•˜๋Š” ๊ทผ๋ณธ์ ์ธ ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ์ด์ „์— ์ง„ํ–‰ ์ค‘์ด๋˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํŽ˜์น˜(Refetch)๊ฐ€ ํ•œ๋ฐœ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•˜์—ฌ, ์šฐ๋ฆฌ๊ฐ€ ์ˆ˜๋™์œผ๋กœ ๊น”์•„๋‘” ๋‚™๊ด€์  ์บ์‹œ(๊ฐ€์งœ ์ƒˆ ๋ฐ์ดํ„ฐ)๋ฅผ ๊ณผ๊ฑฐ์˜ ๋‚ก์€ ๋ฐ์ดํ„ฐ๋กœ ๋ฎ์–ด์”Œ์›Œ๋ฒ„๋ฆฌ๋Š” ์ฐธ์‚ฌ(Race Condition)๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: ๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅด๊ธฐ 0.1์ดˆ ์ „์— ๋‹ค๋ฅธ ์ด์œ (์˜ˆ: ํƒญ ์ด๋™)๋กœ ['posts'] ์ฟผ๋ฆฌ๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํŒจ์น˜๋ฅผ ์ด ๋‘” ์ƒํƒœ์˜€๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ํ•˜ํŠธ ๊ฐœ์ˆ˜๋ฅผ ๊ฐ€์งœ๋กœ +1 ํ•ด๋‘์—ˆ๋Š”๋ฐ, ๊ทธ ์งํ›„์— ์•„๊นŒ ์ˆ๋˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํŒจ์น˜์˜ ์‘๋‹ต์ด ๋„์ฐฉํ•ด๋ฒ„๋ฆฌ๋ฉด ์บ์‹œ๋Š” ๋‹ค์‹œ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ(ํ•˜ํŠธ ๊ฐœ์ˆ˜ -1) ์ƒํƒœ๋กœ ๋ฎ์–ด์”Œ์›Œ์ ธ ๋ฒ„๋ฆฌ๋ฉฐ ํ™”๋ฉด์˜ ํ•˜ํŠธ๊ฐ€ ๊ปŒ๋ป‘๊ปŒ๋ป‘์ด๋Š” ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, '์–ด์ฐจํ”ผ ๋‚™๊ด€์ ์ธ๋ฐ ๋’ท๋‹จ์—์„œ ๋ญฃํ•˜๋Ÿฌ ๋„๋Š” ๊ฑฐ ์ทจ์†Œํ•ด์š”?' ๋ผ๋‡จ! ์ˆ˜๋™ ์กฐ์ž‘(setQueryData)์„ ํ•  ๋•Œ ์บ์‹œ์˜ ์†Œ์œ ๊ถŒ์€ ํ˜„์žฌ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ณผ๊ฑฐ์˜ ๋ง๋ น(์ด์ „ ํŽ˜์นญ ๊ฒฐ๊ณผ)์ด ํ•จ๋ถ€๋กœ ์นจ๋ฒ”ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋ฌธ์„ ์ž ๊ทธ๋Š”(Cancel) ํ–‰์œ„๋Š” ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ์˜ ์ œ1 ์›์น™์ž…๋‹ˆ๋‹ค!"
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๋‚™๊ด€์  ์ˆ˜๋™ ๋ฎ์–ด์“ฐ๊ธฐ ์ „์—” ๋ฌด์กฐ๊ฑด ๋‚จ์€ ์ž”์—ฌ ์ฟผ๋ฆฌ๋“ค Cancel ๋น” ์˜๊ธฐ!