일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 클라이언트 컴포넌트
- 중복카테고리
- 항해99솔직후기
- 백준
- 로딩 후 실행
- server component
- NextJS v13
- 탐욕알고리즘
- 항해99후기
- 배열 메소드
- 항해99추천
- 배열 중복 제거
- react
- 프로그래머스
- 중복선택
- 동전 0
- JavaScript
- 항해99
- 부트캠프항해
- 알고리즘
- 날씨 api
- 서버 컴포넌트
- 실전프로젝트
- db수정
- 카테고리필터
- 숫자를 별점으로
- 그리디
- 자바스크립트
- jQuery
- greedy
- Today
- Total
공부 및 일상기록
Optimistic update를 적용하다. 본문
진행중인 일상의 방탈출 프로젝트 리팩토링 중 마이페이지부분을 수정하다가 다음과 같은 문제에 마주쳤다.
설명하기 앞서 어떤 기능을 하는지 설명하자면..
여러개의 내가 하고 싶은 방탈출을 찜 해둔다.
그러면 마이페이지에서 내가 찜한 테마 목록을 통해 찜했던 데이터를 모두 볼 수 있다.
이제부터는 내가 마주한 문제이다.
내가 찜한 테마 목록 페이지는 무한스크롤이 구현되어있었다. 그리고 찜하기 해제 버튼을 누르면 해당 테마가 목록에서 사라지도록 구현하는 아주 간단한 기능의 페이지 이다.
그런데.. 무한스크롤로 약 3개 페이지정도 불러온 뒤 찜하기를 해제하면 약간의 딜레이가 걸린 후 목록에서 사라지게 되었다.
그 이유는 다음과 같았다.
먼저 wish가 찜하기 및 찜하기 해제를 동시에 하는 api인데, wish 자체의 response에는 찜하기나 찜해제가 성공했다는것 뿐이다.
그럼 어떻게 내가 찜한 목록에서 지워줄까?
내가 구현했던 방식은 찜하기해제를 성공하면 무한스크롤을 구현한 useInfiniteQuery를 무효화하고 다시 서버데이터를 페칭하여 즉각 서버상태와 동일하게 맞추는 방법을 선택했다.
그러다보니,, 버튼을 누르면 가는 요청 더하기 이미 불러왔던 페이지들을 다시 한번씩 불러오는 과정을 거치면서 3페이지 기준 약 250ms의 딜레이가 생긴 뒤 화면에서 사라지기 때문이였다..
그래서 이런 딜레이를 줄일수 있는 방법을 찾을 순 없을까 하고 찾아본 결과 Optimistic update라는 기법을 알게되었다.
Optimistic update
먼저 React query에서 말하는 optimistic update란 직역하면 낙관적 업데이트이다.
무슨말이냐? 바로 서버로 보낸 요청이 무조건 성공할거야 라고 낙관적으로 생각하고 업데이트를 진행한다는 말이다.
그래서 이게 뭐가 좋은거냐?? 라고 한다면 위에서 나에게 발생했던 문제처럼 딜레이를 기다릴 필요가 없다는 말이 된다.
일반적으로 요청을 보내고 서버에서 처리한 뒤 데이터를 받아 업데이트 되지만, 이 방식은 서버에 보냈으니까 어차피 업데이트 될 것이고, 그럼 미리 UI를 바꿔놔도 되겠군 하는 방식이라는 것이다.
딱 내가 겪고있는 문제를 해결하기 좋은 방식이였다.
이 좋은 방식을 사용하는 방법은 다음과 같다.
useMutation의 옵션으로 onMutate를 사용하면 된다.
onMutate란 useMutation의 mutationFunction (서버와 통신하는 함수)가 실행되기 전에 실행되는 기능이다. 따라서 서버와의 통신이 성공하기를 바라며 미리 업데이트를 시킬 수 있다는 것이다. (이것이 optimistic update)
만약 실패한다면 onError를 통해 낙관적 업데이트를 다시 롤백해야 한다.
정확한 사용법은 https://tanstack.com/query/latest/docs/react/reference/useMutation 공식홈페이지에서 확인하면 아주 자세하게 나온다.. 그래도 혹시나 뭔지 잘 모르겠다면 아래 내 코드를 봐도 좋다. 하지만 분명히 데이터 구조가 다를것이므로 전체적인 뼈대만 보고 내부의 데이터를 변형하는건 참고하지 않길 바란다.
const themeLike = useMutation((themeId: number) => wishTheme({ themeId }), {
onMutate: async (themeId) => {
// Optimistic update: 로컬 데이터를 미리 업데이트
await queryClient.cancelQueries(["myThemes"]); // 현재 실행 중인 쿼리를 취소
const previousData = queryClient.getQueryData(["myThemes"]); // 현재 쿼리 데이터를 저장
queryClient.setQueryData(["myThemes"], (oldData: any) => {
return {
...oldData,
pages: oldData.pages.map((pageData: any) => {
return {
...pageData,
content: pageData.content.filter(
(theme: ThemeDataType) => theme.id !== themeId
),
};
}),
};
});
return { previousData };
},
onError: (err, variables, context) => {
// 서버 요청이 실패한 경우, 로컬 데이터를 이전 상태로 롤백
if (context?.previousData) {
queryClient.setQueryData(["myThemes"], context.previousData);
}
},
onSuccess: () => {
queryClient.invalidateQueries(["myThemes"]);
},
});
약간의 설명을 하자면, 해당 mutation은 찜한 테마목록에 있는 테마의 찜하기 버튼을 눌렀을 때 동작하는 것으로, 해당 mutation이 불리면 (찜해제를 누르면), onMutate 내부의 로직이 실행된다.
onMutate내부에서는 먼저 cancleQueries를 이용하여 낙관적 업데이트를 하고싶은 데이터의 쿼리를 중단시킨다.
저것을 중단시키지 않으면 쿼리가 완료되어 백엔드에서 데이터가 와버리면 우리가 업데이트 하려는것이 무시되기 때문이다.
그리고 해당 쿼리의 데이터를 getQueryData로 가져온 뒤, setQueryData를 통해 원하는 형식으로 바꾸면 된다. 내 코드를 보면 나는 서버데이터 안에 pages라는 page배열이 있고, page안에는 content라는 테마정보데이터 배열이 있는 형식이다. 그래서 해당 테마 Id를 가진 데이터를 filter하여 삭제시키는 동작을 한다.
'개발 > TIL WIL 공부목표' 카테고리의 다른 글
이벤트 버블링을 이용해보기 (0) | 2023.07.15 |
---|---|
쿼리스트링 이용하기 (0) | 2023.07.13 |
useRef를 활용한 렌더링 방지 (0) | 2023.06.21 |
[기능구현] JS 배열 랜덤하게 구성하기 (fisher-yates) (0) | 2023.06.21 |
[trouble-shooting] styled-components에서 keyframe 변수 공유 문제 (0) | 2023.06.21 |