23. Mutation 처리 패턴
23. Mutation 처리 패턴

23. Mutation 처리 패턴

Authors
Date
Jun 8, 2025
Tags
frontend
Published
Published
Slug
📝
주관적인 생각이 많이 담겨있는 글입니다
 

1. Mutation이란 무엇인가?

서버에 데이터를 변경 요청하는 작업"mutation"이라고 부른다.
예를들어, 사용자가 게시판에서 게시글을 작성하고, ‘작성 완료’ 버튼을 눌렀을때 이뤄지는 작업을 말한다.
구현 방식에 따라 다르겠지만, 이 단순해 보일수도 있는 작업에서 아래와 같은 흐름이 나타난다
1. 사용자가 제목, 게시글 내용 등을 입력함 (필드 입력) 2. 사용자가 '작성 완료' 버튼을 클릭함 (submit) 3. 입력된 게시글이 즉각적으로 등록된것으로 보이도록 업데이트함 (optimistic update) 4. 입력된 필드에 문제가 있으면 에러를 표시함 (validation error) 5. 서버에서 에러가 발생하면 에러를 표시함 (error) 6. 4또는 5이 만족하면 3에서 수행한 업데이트를 롤백함 (rollback) 7. 입력된 정보가 서버에 저장됨 (mutation)
 
 
게시글 등록을 예로 들었지만, form을 사용하는 다른 mutation작업에서도 위의 흐름과 크게 다르지 않은 경우가 다수 존재한다.
ex) 댓글 등록, 프로필 수정 등
 
흐름을 잘 살펴보면, 다른 mutation작업에서도 공통적으로 수행되어야하는 부분이 있다.
“2~7번을 자동적으로 수행하도록 구현할 수 있다면, 다른 mutation작업도 쉽게 구현할 수 있겠다”는 아이디어가 이번 글의 핵심 주제이다.
 
 
내용을 살펴보면 수행되야 하는 작업은 크게 두가지로 생각해볼 수 있다.
  1. 캐시 업데이트 (+낙관적 업데이트)
  1. 에러 표시
 
그리고 이 글에서는 1번에 대해서만 다룬다.

 

2. 캐시 업데이트란?

캐시 업데이트를 수행하지 않는다면, 새롭게 업데이트된 항목이 있어도 이전의 데이터를 보여주게 된다.
📝
예시:
  1. 게시글을 새로 등록함
  1. 새로운 데이터가 있지만, 게시글 리스트는 업데이트 되지 않고 이전의 데이터들만 보여줌
 

2.1 Problem

아래 예제에는 좌측에 게시글 리스트가 보여지고있다.
우측에는 제목과 내용을 입력하여 새로운 게시글을 등록할 수 있는 form이 있다.
개발자 도구를 열고 form에 제목과 내용을 추가하여 게시글을 등록해보자
일정확률로 에러가 발생하며, 게시글이 성공적으로 등록되었다면 201 응답이 올 것이다.
 
하지만 201 응답이 발생했음에도 좌측 리스트에서 새로운 게시글이 보이지 않을것이다.
그 이유는 form 제출에 상관없이 리스트는 같은 데이터만을 보여주고 있기 때문이다.
 
 

2.2 Solution #1 - Refetch(Invalide Queries)

앞선 해결하기 위한 대중적인 방법으로, queryClient.invalidateQueries로 쿼리를 stale상태로 만들어 refetch를 유도할 수 있다.
 
하지만 이방식에서는 게시글 등록과 refetch 총 두번의 네트워크 왕복이 발생하게 된다.
notion image
 

2.3 Solution #2 - 캐시 업데이트(setQueryData)

refetch하여 새롭게 데이터를 가져오는것 대신에 response body를 활용할 수 있다.
새로운 데이터를 POST요청의 response body로 전달하고, setQueryData매소드로 캐시를 업데이트 할 수 있다.
이렇게 캐시의 업데이트를 수행한다면, 한번의 네트워크 왕복으로 수행이 가능하다.
notion image
 

2.4 Solution #3 - Optimistic Update

낙관적 업데이트를 추가하여 사용자 경험을 개선할 수 있다.
POST요청에 대한 응답이 오기전에 form에 입력한 데이터를 가지고 게시글 리스트 데이터를 업데이트 하는 방법이다.
네트워크 요청의 딜레이를 기다릴 필요없이 UI가 즉각적으로 업데이트 된다.
물론, 불완전한 데이터이기 때문에 기존 데이터와 병합시 주의가 필요하다.
notion image
 

3. 구현

캐시 업데이트와 낙관적업데이트를 수행하는 추상화 훅(useSmartMutation)을 아래와 같이 구현할 수 있다
useSmartMutation.ts
entrolEC

+ isCollection은 무엇인가?

캐시 업데이트 및 낙관적 업데이트를 수행할때 각 쿼리에 데이터가 콜랙션인지, 단건인지에 따라 구현에 차이가 생긴다.
// 콜랙션 // GET /api/post [ { "id": 1, "title": "mutation 패턴", "description" : "..." }, { "id": 2, "title": "useSuspenseQuery란?", "description" : "..." }, { "id": 3, "title": "use 훅", "description" : "..." }, ] // 단건 // GET /api/post/1 { "id": 1, "title": "mutation 패턴", "description" : "..." }
 
콜랙션에서 새로운 데이터가 추가된다면, 리스트에 기존 데이터들을 남겨두고 새로운 데이터를 추가한다.
[ { // 기존 데이터들은 남기고 "id": 1, "title": "mutation 패턴", "description" : "..." }, { "id": 2, "title": "useSuspenseQuery란?", "description" : "..." }, { "id": 3, "title": "use 훅", "description" : "..." }, { // <-- 마지막에 새로 추가함 "id": 4, "title": "서버컴포넌트", "description" : "..." } ]
단건에서 새로운 데이터가 추가된다면 기존의 데이터를 대체하는 방식으로 구현하기 위해
콜랙션 데이터 여부에 따라 별도로 분기처리를 하여 구현하였다.
 

3.1 사용방법

아래와 같이 작성한다면 캐시 업데이트 및 낙관적 업데이트를 수행하는 mutation을 만들 수 있다.
const mutation = useSmartMutation({ queryKey: ['posts'], mutationFn: createPost, isCollection: true }); const onSubmit = (data: CreatePostInput) => { mutation.mutate(data); };
post-form.tsx
entrolEC

4. 결론

  • 여러 mutation과정에서 비슷한 흐름이 나타난다.
  • 대표적으로 캐시 업데이트와 낙관적 업데이트, 에러 핸들링이 있다.
  • 이 과정들을 하나의 패턴으로 정의하고 한번에 처리할수 있는 추상화 훅을 만든다면, 효율적으로 mutation을 구현할 수 있다.