Optimistic Update(낙관적 업데이트)

hyeryeon·2024년 9월 3일

React

목록 보기
5/19

현재 문제점은...
사용자가 데이터를 업데이트(수정)하고 나서도 페이지를 새로 고치지 않으면 업데이트된 정보가 화면에 반영되지 않는 문제가 있다. 사용자는 수정한 내용을 곧바로 확인하고 싶어 하지만, 현재 시스템에서는 백엔드 서버의 응답을 기다리고 페이지를 새로 고쳐야만 수정된 내용이 보인다는 것

예시 문제 사항 :
좋아요 버튼을 눌렀을 때 서버에서 데이터를 가져오는 속도가 느려 사용자 경험이 안좋아졌다.

낙관적 업데이트란?

낙관적 업데이트는 사용자가 데이터를 변경하면, 백엔드의 응답을 기다리지 않고 UI에서 바로 변경 사항을 반영하는 방식이다. 이렇게 하면 사용자 경험이 개선되며, UI가 빠르게 반응한다.
만약 백엔드에서 업데이트가 실패하면, UI에서 이미 반영된 변경 사항을 이전 상태로 롤백

공식 문서 : https://tanstack.com/query/v5/docs/framework/react/guides/optimistic-updates

React Query로 낙관적 업데이트 구현하기

아래는 useMutation 함수의 onMutate 옵션을 사용해서 낙관적 업데이트의 기본틀이다.

const queryClient = useQueryClient()

useMutation({
mutationFn: updateFn,
 onSuccess: () => {
	// invalidateQueries (쿼리 무효화)
},
 onMutate: async () => {
	// cancelQueries (쿼리 요청 취소)
    // getQueryData (이전 데이터 요청)
    // setQueryData (낙관적 업데이트 진행)
    // return getQueryData 에서 가져온 값 ( onError에서의 prevData )
},
 onError: (err, variales, prevData) => {
	// setQueryData (실패 시 이전 데이터 전환)
},
});

이제 실제 코드에 적용해보자

(1) useMutation 설정

useMutation 훅은 데이터를 변형(예: 생성, 업데이트, 삭제)하는 데 사용됩니다. 여기서는 이벤트 데이터를 업데이트하기 위해 사용됩니다.


const {mutate} = useMutation({
  queryFn: updateEvent, // 실제 데이터를 수정하는 함수
  queryFn:({signal}) => fetchEvent({signal,id:params.id})
})

(2) onMutate: 낙관적 업데이트 시작

onMutate는 사용자가 데이터를 수정하고 서버로 요청이 보내지는 즉시 실행되는 함수입니다. 백엔드 서버의 응답을 기다리지 않고, UI를 즉시 업데이트하는 역할을 합니다.


onMutate: async (data) => {
  const newEvent = data.event; // 사용자가 입력한 새로운 데이터

  // 현재 쿼리를 취소하여 충돌을 방지
  await queryClient.cancelQueries({ queryKey: ['events', params.id] });

  // 이전 데이터를 가져와 나중에 롤백할 수 있도록 저장
  const previousEvent = queryClient.getQueryData(['events', params.id]);

  // 응답을 기다리지 않고 새로운 데이터로 캐시를 업데이트
  queryClient.setQueryData(['events', params.id], newEvent);

  return { previousEvent }; // 이전 데이터를 반환하여 롤백 시 사용할 수 있도록 함
}
  • 데이터 가져오기: data.event에서 사용자가 입력한 새 데이터를 가져온다.

  • 진행 중인 쿼리 취소:
    await queryClient.cancelQueries({ queryKey: ['events', params.id] })는 현재 진행 중인 같은 이벤트 ID와 관련된 다른 쿼리를 취소한다. 이는 서버의 응답이 도착하기 전에 UI가 잘못된 데이터를 표시하는 것을 방지하기 위함이다.

  • 이전 데이터 저장: previousEvent에 현재 캐시된 데이터를 저장한다. 나중에 만약 업데이트가 실패하면, 이 데이터를 사용해 UI를 이전 상태로 되돌릴 수 있다.

  • UI 즉시 업데이트:
    queryClient.setQueryData(['events', params.id], newEvent)는 사용자가 입력한 새 데이터를 즉시 UI에 반영하는데 이때, 서버의 응답을 기다리지 않는다.

  • 이전 데이터 반환: 반환된 previousEvent는 업데이트 실패 시, 롤백하는 데 사용된다.

(3) onError: 업데이트 실패 시 롤백

onError 함수는 서버에서 오류가 발생해 데이터 업데이트가 실패했을 때 호출된다. 이 함수에서는 이전 상태로 되돌리는(롤백) 작업을 한다.


onError: (error, data, context) => {
  // 실패 시 이전 데이터로 롤백
  queryClient.setQueryData(['events', params.id], context.previousEvent);
 
}

롤백 작업: context.previousEvent에는 이전 단계에서 저장해둔 이전 데이터가 들어 있습니다. 이 데이터를 사용해 캐시를 다시 업데이트하면, UI가 이전 상태로 되돌아갑니다.

(4) onSettled: 변형 완료 후 처리

onSettled 함수는 변형이 성공하든 실패하든 상관없이 실행됩니다. 여기서는 백엔드와 프론트엔드 데이터가 일치하도록 쿼리를 무효화하여 최신 데이터를 다시 가져옵니다.


onSettled: () => {
  // 변형 완료 후 쿼리를 무효화하여 최신 데이터를 가져옴
  queryClient.invalidateQueries(['events', params.id]);
}

쿼리 무효화: queryClient.invalidateQueries(['events', params.id])는 해당 쿼리를 무효화하여, 다음 번에 다시 데이터를 요청할 때 최신 데이터를 가져오도록 합니다.

전반적인 흐름

  1. 사용자가 데이터를 업데이트하면 mutate가 호출됩니다.
  2. onMutate에서 즉시 UI가 업데이트됩니다. 이때 백엔드의 응답을 기다리지 않습니다.
  3. onError에서 백엔드에서 업데이트가 실패하면, 이전 상태로 롤백합니다.
  4. onSettled에서 변형이 완료된 후, 백엔드와 UI 간의 데이터 동기화를 위해 쿼리를 무효화합니다.

useMutation 구성 요소의 역할

  • queryFn: 실제로 데이터를 업데이트하는 함수 (updateEvent).
  • onMutate: 변형이 시작되기 전에 실행되어, 즉시 UI를 업데이트하고, 충돌을 방지하기 위해 쿼리를 취소하며, 롤백할 데이터를 저장합니다.
  • onError: 변형이 실패하면 롤백을 수행합니다.
  • onSettled: 변형이 끝나면 최신 데이터를 가져오기 위해 쿼리를 무효화합니다.

전체코드

const {mutate} = useMutation({
  queryFn: updateEvent,
  onMutate: async (data) => {
    const newEvent = data.event;
    await queryClient.cancelQueries({ queryKey: ['events', params.id] });
    const previousEvent = queryClient.getQueryData(['events', params.id]);
    queryClient.setQueryData(['events', params.id], newEvent);
    return { previousEvent };
  },
  onError: (error, data, context) => {
    queryClient.setQueryData(['events', params.id], context.previousEvent);
  },
  onSettled: () => {
    queryClient.invalidateQueries(['events', params.id]);
  }
});

오?? 나혼자 너무 어렵게 생각했던 걸로..


https://aotoyae.tistory.com/entry/React-TanStack-Query-Optimistic-Updates-%EC%98%B5%ED%8B%B0%EB%AF%B8%EC%8A%A4%ED%8B%B1-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8

0개의 댓글