일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 가계부채
- Next.js
- 경제신문읽기
- 금투세
- next
- 은행
- 경제
- 코드잇 스프린트
- 코드잇 스프린트 9기
- 개발
- 미국
- js
- 자바스크립트
- CS
- react
- 금융투자소득세
- 리엑트 쿼리
- 기준금리
- 내수
- 프로젝트 회고
- 코드잇
- 프론트엔드
- javascript
- 금리
- 개발자
- API
- typescript
- 가계대출
- 한국은행
- FED
- Today
- Total
뭉균의 개발일지
[React-query] 리엑트 쿼리의 Optimistic Update에 대하여 본문
🚪들어가며
우선, 해당 글은 React-query의 Mutation에 관한 개념을 알고 있다는 가정 하에 작성했습니다😎
인스타그램이나 페이스북을 하다보면 특정 게시물에 좋아요를 누르는 경험을 쉽게 할 수 있습니다. 사용자가 좋아요 버튼을 클릭하면 서버 응답을 기다리지 않고 클라이언트에서 즉시 좋아요 수가 증가합니다. 이때 사용자는 "좋아요가 바로 반영되었구나"라고 느끼지만, 실제로는 서버에 요청이 전송되고 처리 중인 상태일 수 있습니다. 이처럼 서버가 요청을 성공적으로 처리할 것이라고 가정(낙관적인 가정)하고, 먼저 클라이언트 상태를 변경하는 것을 Optimistic Update라고 합니다.
Optimistic Update 적용 전
서버에서 좋아요 상태가 업데이트되고 응답이 클라이언트로 돌아올 때까지 UI는 변하지 않습니다. 이 과정에서 사용자에게 약간의 지연이 느껴질 수 있습니다.
Optimistic Update 적용 후
사용자가 버튼을 누르는 즉시 UI 상에서 좋아요가 반영됩니다. 동시에 서버에 요청을 보내고, 서버가 요청을 성공적으로 처리했다고 응답하면 상태가 유지됩니다. 요청이 실패할 경우, "좋아요" 상태를 다시 원래대로 롤백합니다.
🖥️ 예시
좋아요 상태 업데이트 훅 useLike를 통해서 Optimistic Update 과정을 더 자세히 살펴보겠습니다. 현재 posts라는 queryKey 안에 좋아요 상태가 담겨져있는 상황입니다. 우선, 전체 코드부터 보여드리겠습니다.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateLikeStatus } from "@/lib/api/like"; // 좋아요 상태를 업데이트하는 API 함수
// 좋아요 상태 업데이트 훅
const useLike = () => {
const queryClient = useQueryClient();
const Toast = useToast();
const mutation = useMutation({
mutationFn: updateLikeStatus, // 좋아요 상태를 업데이트하는 API 함수
onMutate: async ({ postId, newLikeStatus }) => {
// 1. 쿼리 요청이 진행 중이라면 취소 (Optimistic Update로 인해 덮어쓰이지 않도록)
await queryClient.cancelQueries({ queryKey: ["posts"] });
// 2. 이전 데이터를 스냅샷으로 저장
const previousPosts = queryClient.getQueryData(["posts"]);
// 3. Optimistic Update로 UI를 즉시 변경
queryClient.setQueryData(["posts"], (oldPosts) => {
return oldPosts.map((post) =>
post.id === postId
? {
...post,
likeStatus: newLikeStatus, // 좋아요 상태 업데이트
likeCount: newLikeStatus ? post.likeCount + 1 : post.likeCount - 1 // 좋아요 수 업데이트
}
: post
);
});
// 4. 롤백을 위해 이전 데이터를 반환
return { previousPosts };
},
onError: (err, variables, context) => {
// 1. 오류 발생 시 이전 데이터를 롤백
queryClient.setQueryData(["posts"], context.previousPosts);
console.error("좋아요 업데이트 중 오류가 발생했습니다.");
},
onSettled: () => {
// 2. 성공/실패 여부와 관계없이 데이터를 새로고침
queryClient.invalidateQueries({ queryKey: ["posts"] });
},
onSuccess: () => {
// 3. 좋아요 상태가 성공적으로 변경되면 성공 메시지
console.log("좋아요 상태가 업데이트되었습니다!");
},
});
return mutation;
};
export default useLike;
이제 각 단계를 분리해서 보겠습니다.
📌 onMutate
새로운 데이터를 추가하거나 변경하기 직전에 실행되는 단계입니다.
onMutate: async ({ postId, newLikeStatus }) => {
// 1. 쿼리 요청이 진행 중이라면 취소 (Optimistic Update로 인해 덮어쓰이지 않도록)
await queryClient.cancelQueries({ queryKey: ["posts"] });
// 2. 이전 데이터를 스냅샷으로 저장
const previousPosts = queryClient.getQueryData(["posts"]);
// 3. Optimistic Update로 UI를 즉시 변경
queryClient.setQueryData(["posts"], (oldPosts) => {
return oldPosts.map((post) =>
post.id === postId
? {
...post,
likeStatus: newLikeStatus, // 좋아요 상태 업데이트
likeCount: newLikeStatus ? post.likeCount + 1 : post.likeCount - 1 // 좋아요 수 업데이트
}
: post
);
});
// 4. 롤백을 위해 이전 데이터를 반환
return { previousPosts };
}
1. 기존 쿼리 요청 중단
queryClient.cancelQueries를 호출하여 기존 쿼리 요청을 취소합니다. 이렇게 하면 서버로부터의 새로운 데이터가 클라이언트에서 설정한 Optimistic Update를 덮어쓰지 않습니다.
2. 기존 데이터 스냅샷 저장
queryClient.getQueryData를 사용하여 현재 데이터를 스냅샷으로 저장합니다. 이는 나중에 롤백할 경우를 대비한 안전장치 역할을 합니다.
3. 낙관적으로 클라이언트 상태를 업데이트
queryClient.setQueryData를 사용해 기존 데이터에 새로운 데이터를 추가하거나 수정한 값을 적용합니다. 이 단계에서 UI는 서버 응답 없이 변경된 상태를 보여줍니다.
4. 컨텍스트 반환
이전 상태(스냅샷)를 포함한 컨텍스트를 반환합니다. 이는 실패 시 롤백에 사용됩니다.
📌 onError
요청이 실패했을 경우 실행되는 단계입니다.
onError: (err, variables, context) => {
// 1. 오류 발생 시 이전 데이터를 롤백
queryClient.setQueryData(["posts"], context.previousPosts);
console.error("좋아요 업데이트 중 오류가 발생했습니다.");
}
1. 클라이언트 상태 롤백
에러 발생 시, onMutate에서 저장한 컨텍스트를 이용해 데이터를 원래 상태로 복원합니다. queryClient.setQueryData를 호출하여 저장해둔 previousPosts 값으로 롤백합니다.
2. 에러 처리
필요에 따라 사용자에게 에러 메시지를 보여줄 수 있습니다.
📌onSettled
요청이 성공하거나 실패한 후 항상 실행되는 단계입니다.
onSettled: () => {
// 2. 성공/실패 여부와 관계없이 데이터를 새로고침
queryClient.invalidateQueries({ queryKey: ["posts"] });
}
데이터 리패치
queryClient.invalidateQueries를 호출하여 서버 데이터를 다시 가져옵니다. 이는 클라이언트 상태가 서버 상태와 정확히 일치하도록 보장합니다.
이 과정들을 거치며, Optimistic Update 개념을 적용할 수 있습니다.
📚 결론
Optimistic Update는 사용자 경험을 향상시키는 중요한 기술 중 하나입니다. 서버의 응답을 기다리지 않고 클라이언트에서 즉시 상태를 반영하여 사용자에게 빠른 피드백을 제공합니다. 기본적으로 useMutation만 사용해도 서버 데이터를 화면에 반영할 수 있지만, 이 방법은 서버의 응답을 기다려야 하므로 화면에 반영되는 데 시간이 걸릴 수 있습니다. 이때 Optimistic Update를 사용하면, 서버 응답을 기다리지 않고 클라이언트에서 즉시 UI를 업데이트하여 사용자에게 더 빠르고 매끄러운 경험을 제공합니다.
이번 글에서는 좋아요 상태 업데이트를 예시로 Optimistic Update가 어떻게 동작하는지 살펴보았습니다. onMutate, onError, onSettled 등의 콜백을 적절히 활용하면, 클라이언트에서 빠르게 UI 변화를 주고 서버와의 동기화를 유지할 수 있습니다. 오류가 발생했을 때도 이전 상태로 롤백할 수 있는 안전장치를 마련하여 안정성을 높일 수 있습니다.
결국, Optimistic Update는 서버 응답을 기다리는 동안 발생할 수 있는 사용자 경험의 지연을 줄여주며, 빠르고 매끄러운 인터랙션을 제공하는 데 큰 도움이 됩니다. 이 방법을 활용하여 더 나은 사용자 경험을 제공하는 웹 애플리케이션을 개발할 수 있습니다.
📚 참고 자료
1. https://tanstack.com/query/v4/docs/framework/react/guides/optimistic-updates
'React' 카테고리의 다른 글
[React] 메모이제이션(useMemo, useCallback, React.Memo) (3) | 2024.09.26 |
---|