Hello It's good to be back ^_^

[UMC 블로그챌린지] Web 파트 7주차 - useMutation과 Optimistic Update 본문

Study/Web

[UMC 블로그챌린지] Web 파트 7주차 - useMutation과 Optimistic Update

HongyeonLee 2026. 5. 11. 20:27

 

Mutation (데이터 변경)

: 클라이언트가 서버 데이터를 바꾸는 것 ex. 좋아요, 댓글 등

 

Pessimistic Update 

: 데이터 변경을 서버 응답을 기다린 후 반영하는 것, 구현은 단순하고 정합성이 보장되지만, 사용자가 매번 로딩을 기다려야 함

 

Optimistic Update (낙관적 업데이트)

: 데이터 변경을 먼저 UI를 바꾸고 서버 응답으로 검증하는 것, 사용자 경험은 즉각적이지만, 실패 시 롤백 처리와 동시성 제어가 까다로워진다

 

useMutation()

  • Mutation이 일어나면 서버에는 반영되나 화면(클라이언트)에는 반영되지 않기에 개발자는 쿼리를 실행에 실제 데이터베이스에 추가해야 한다
  • 이전에는 Mutation이 일어날 때 마다 개발자는 일일히 쿼리를 수동으로 다시 실행하는 코드를 매번 직접 작성해야 했다
  • useMutation은 이러한 서버에 데이터를 변경(post/put/delete)하는 작업을 표준화 했다

useMutation의 속성

속성 역할
mutationFn 실제로 서버에 변경 요청을 보내는 비동기 함수(post/put/delete 담당) ex.createTodo
isPending 요청이 진행중 인지 ex.추가 중...
isError 요청이 실패 했는지
onSuccess 요청이 성공한 직후 실행되는 함수 (가장 중요)
.mutate() - 변경 작업을 실행하는 함수 (여기에 payload를 전달함)

 

실행 흐름

  const create = useMutation({
    mutationKey: ['createTodo'],
    mutationFn: createTodo,
    // 🔥 Mutation의 핵심!
    // 1. 서버 변경(POST) 성공 시 실행. (네트워크 요청 성공)
    // 2. ['todos'] 키를 가진 캐시를 '만료됨(Stale)'으로 표시합니다. (invalidateQueries)
    onSuccess: () => qc.invalidateQueries({ queryKey: ['todos'] }),
  });
  1. 사용자가 todo를 작성하고 추가 버튼 클릭 → create.mutate() 실행
  2. createTodo 함수가 서버에 요청을 보냄 (진행 중: create.isPending = true)
  3. 서버 요청 성공 → onSuccess 실행
  4. qc.invalidateQueries({ queryKey: ['todos'] })가 실행되자마자 TanStack Query가 TodoList 컴포넌트가 사용하는 ['todos'] 데이터가 오래됐으니 업데이트하라고 표시
  5. TanStack Query가 자동으로 fetchTodos 함수를 재실행(Re-fetch)하여 최신 목록을 가져오고 화면에 자동을 반영
  6. 개발자가 수동으로 fetchTodos를 다시 호출할 필요가 없음

라이프사이클 옵션

: 데이터 변경 요청이 이루어지는 전 과정을 세밀하게 제어하는 도구들로 데이터 변경 요청에 따라 변경 전/중/후를 제어

옵션 실행 시점 용도
onMutate 요청이 서버로 보내지기 직전에 호출 -낙관적 업데이트를 할 때, 요청에 따라서 UI를 먼저 바꿨는데 요청이 실패할 경우 롤백할 데이터(=context)를 미리 저장
-캐시 데이터를 즉각 변경한다 (setQueryData 이용)
onSuccess 요청이 성공적으로 완료되었을 때 호출 데이터를 동기화함
invalidateQueries를 사용해서 목록을 자동으로 새로고침하게 만듦
onError 요청 도중 에러가 발생했을 때 호출 -에러 메세지를 사용자에게 보여주고 onMutate에서 변경했던 데이터(=context)를 롤백할 때 사용
-context를 꺼내와서 setQueryData로 캐시 복구
onSetteled 성공과 실패여부에 상관없이 모든 작업이 끝났을 때 호출 -마지막으로 로딩 상태를 해제하거나 최종 정리 작업을 할 때 사용
-성공 여부와 상관없이 목록 새로고침(invalidateQueries)해서 서버의 최종 상태와 클라이언트를 동기화

 

설정 옵션

: 안정성, 제어 강화

옵션 용도
retry Mutation이 실패했을 때 몇 번까지 자동으로 다시 시도할지 정함
retryDelay 재시도 사이에 얼마나 기다릴지 시간을 정함
throwOnError 에러가 났을 때 TanStack Query 내부에서 에러를 잡지 않고 밖으로 던질지 정함
meta Mutation에 대한 추가 정보를 넣음

 

 

Optimistic Update (낙관적 업데이트)의 장단점

 

장점

  • 즉각적 반응으로 사용자 경험이 좋아짐
  • 네트워크 지연을 무시 (사용자의 네트워크 속도와 상관없이 빠른 반응)

단점

  • 서버 요청이 실패했을 경우 롤백해야 하므로 로직이 복잡해짐
  • 일시적으로 클라이언트 화면과 서버의 실제 데이터가 다를 수 있음

 

언제 낙관적 업데이트를 사용해야 할까?

  • 사용자 행동이 성공할 확률이 매우 높고 UI 피드백이 즉각적이어야 만족도가 높을 때
  • 단순 데이터 변경 - 좋아요, 북마크, ToDo 완료 체크 등
  • 단독 작업 - 다른 사용자와 충돌 가능성이 적은 개인적인 데이터 수정
  • 지연시간이 느린 네트워크 - 사용자가 답답함을 느끼는 네트워크에서 이를 가릴 수 있음

언제 낙관적 업데이트 사용을 피해야 할까?

  • 데이터의 정확성이 중요하고 실패 가능성이 빈번할 때
  • 결제 및 송금 - 반드시 서버의 확정 응답 필요
  • 복잡한 비즈니스 로직 - 서버의 추가적인 검증(재고 확인, 권한 체크 등)을 거쳐야만 결과가 확정되는 경우
  • 충돌 가능성이 높은 동시 편집 - 여러 명이 동시에 수정해서 서버에서 데이터 병합이 복잡하게 일어나는 경우

TanStack Query 기반 구현 흐름 (onMutate → mutate → onError → onSettled)

  • onMutate:
    • mutate 함수가 호출될 때 즉시 실행
    • 진행 중인 refetch를 취소(cancelQueries)하여 낙관적 업데이트가 덮여쓰여지지 않게 한다
    • 현재 캐시 데이터를 백업(Snapshot)합니다
    • setQueryData를 사용해 캐시를 새로운 값으로 강제 업데이트
    • 백업된 데이터를 context로 반환합니다.
  • (mutate):
    • 실제 서버로 API 요청을 보낸다
  • onError:
    • 서버 요청이 실패했을 때 실행된다
    • onMutate에서 반환한 context(스냅샷)를 사용하여 캐시를 이전 상태로 복구한다
  • onSettled:
    • 성공/실패 여부와 상관없이 마지막에 실행
    • invalidateQueries를 호출하여 서버의 최신 데이터와 동기화(Refetch)를 진행한다