[Tanstack query] Optimistic UI 구현하기

준성·2024년 10월 19일
1
post-thumbnail

⚡️ Optimistic UI(낙관적 UI)란?

Optimistic UI는 웹사이트나 앱에서 사용자가 어떤 동작을 했을 때, 모든 API 통신이 성공할 것이라 여기고 화면에 먼저 결과를 그리는 것을 말한다.

예시

  1. 일반적인 방식:
    • 버튼을 누른다.
    • 클릭 함수가 실행되고 요청을 보낸다.
    • 서버에서 확인이 오면 화면이 변한다.

  1. 낙관적 UI 방식:
    • 버튼을 누른다.
    • 바로 화면이 변한다.
    • 서버와 통신한다.

서버가 느려도 사용자는 자신의 동작 결과를 바로 볼 수 있기 때문에 사용자 경험을 향상시킬 수 있다. 하지만 서버에 문제가 생기면 보여준 결과를 취소해야 하고 결제 등과 같은 경우에는 확인 후 보여줘야 한다.

이미지 참고 : Optimistic UIs in under 1000 words

⚡️ Tanstack query를 사용하여 Optimistic UI 구현

프로젝트를 진행하면서 카드 컴포넌트 구현 중 참여하기 버튼 클릭 시 API는 정상 작동하나 UI가 즉시 업데이트되지 않아 사용자 경험 저하되는 문제가 있었다.

Tanstack Query의 setQueryData를 활용하여 즉각적인 UI 업데이트 구현하고, invalidateQueries를 사용하여 서버 데이터와 클라이언트 캐시 동기화했다.

invalidateQueries

invalidateQueries 메서드는 캐시된 쿼리를 무효화하고, 활성 상태의 쿼리는 즉시 refetch되어 서버의 최신 데이터를 가져온다.
서버의 최신 데이터를 가져올 수 있어 데이터 일관성을 유지할 수 있지만 추가적인 네트워크 요청이 발생할 수 있다.

setQueryData

setQueryData 메서드는 캐시 된 쿼리 데이터를 수동으로 업데이트한다.
네트워크 요청 없이 즉각적인 UI 업데이트가 가능하지만 서버와 클라이언트 데이터 간의 불일치가 발생할 수 있다.

사용 예시

  const joinMutation = usePostGatheringsJoin({
    onSuccess: (updatedData) => {
      console.log('참여하기 성공', updatedData);

      const newData = {
        ...localData,
        isJoiner: true,
        participantCount: localData.participantCount + 1,
      };

      setLocalData(newData); // 로컬 상태를 즉시 업데이트

      queryClient.setQueryData(['gatherings', data.gatheringId], newData); // 캐시에 있는 데이터를 즉시 업데이트
      queryClient.invalidateQueries({ queryKey: ['gatherings'] }); // 데이터를 다시 가져오게 요청
    },
    onError: (error) => {
      console.error('참여하기 실패', error);
    },
  });

개인적으로, 대부분의 경우에 무효화를 선호해야 한다고 생각합니다. 물론, 사용 사례에 따라 다르지만, 직접 업데이트가 신뢰성 있게 작동하려면 프론트엔드에 더 많은 코드가 필요하며 어느 정도 백엔드의 로직과 중복된 로직이 필요합니다. 예를 들어, 정렬된 목록은 직접 업데이트하기가 꽤 어렵습니다. 업데이트로 인해 내 항목의 위치가 변경되었을 수 있기 때문입니다. 목록 전체를 무효화하는 것이 “더 안전한” 접근 방법입니다.
TKDodo의 Mastering Mutations in React Query

TKDodo의 글에서는 invalidateQueries를 더 안전한 접근 방법이라고 설명한다. 이번 프로젝트에서는 직접 업데이트를 하는 코드가 복잡하지 않아 setQueryData를 사용해 구현했다.

⚡️ Tanstack query로 캐시 관리하기

Optimistic UI를 구현할 때, 데이터가 즉각적으로 반영되도록 Tanstack query의 캐시 관리를 효율적으로 활용하는 것이 중요하다. 특히 Query Key, staleTime, gcTime, 그리고 refetch와 같은 옵션을 적절하게 설정함으로써, 데이터를 안전하게 관리하고 사용자 경험을 향상시킬 수 있다.

Query Key 설정

Query Key는 캐시 데이터를 고유하게 식별하는 역할을 하며, 캐시된 데이터를 안전하게 업데이트하거나 무효화하기 위해 중요하다.

queryClient.setQueryData(['gatherings', data.gatheringId], newData);
queryClient.invalidateQueries({ queryKey: ['gatherings'] });
  • Query Key는 배열로 설정되며, 각 요소는 해당 데이터를 고유하게 식별하는데 사용된다.

staleTime 설정

staleTime은 캐시된 데이터가 stale(오래된) 상태로 간주되기 전까지의 시간을 정의한다. 기본적으로 0으로 설정되어 있어, 쿼리가 활성화될 때마다 데이터를 다시 가져온다. 하지만 프로젝트 특성에 따라 staleTime을 늘리면 불필요한 네트워크 요청을 줄일 수 있다.

const { data, error } = useQuery(['gatherings', gatheringId], fetchGathering, {
  staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 stale로 간주하지 않음
});
  • 이렇게 설정하면, 5분 동안 데이터가 새로고침되지 않으며 사용자가 더 빠르게 캐시된 데이터를 볼 수 있다.

gcTime 설정 (이전의 cacheTime)

gcTime은 캐시 데이터가 메모리에서 자동으로 삭제되기 전까지 유지되는 시간을 설정한다. 여기서 데이터의 효율적인 재사용, 사용자 경험 개선, 네트워크 요청 최적화 등의 이점을 위해 gcTime을 staleTime보다 길게 설정한다.

const { data, error } = useQuery(['gatherings', gatheringId], fetchGathering, {
  gcTime: 1000 * 60 * 10, // 10분 동안 캐시 유지
});
  • 10분 동안 캐시된 데이터를 메모리에 유지하며, 이 기간 동안 동일한 데이터를 요청하면 네트워크 요청 없이 캐시된 데이터를 사용할 수 있다.

refetch 관련 옵션

Tanstack query는 다양한 refetch 옵션을 제공하여, 특정 조건에서 데이터를 자동으로 다시 가져올 수 있다. 예를 들어, refetchOnWindowFocus 옵션을 통해 브라우저 탭이 다시 활성화될 때 데이터를 새로 고칠 수 있다.

const { data, error } = useQuery(['gatherings', gatheringId], fetchGathering, {
  refetchOnWindowFocus: true, // 윈도우 포커스 시 데이터 새로 고침
});
  • 이 옵션을 활성화하면, 사용자가 다시 브라우저 탭을 활성화했을 때 최신 데이터를 자동으로 가져오게 되어 데이터가 항상 최신 상태를 유지한다.

캐시 관리 전략 요약

  • Query Key: 고유한 데이터 식별을 위한 필수 요소.
  • staleTime: 데이터를 "최신"으로 간주할 시간을 설정하여 네트워크 요청을 최적화.
  • gcTime: 비활성 쿼리 결과가 캐시에서 제거되기까지의 시간을 설정.
  • refetch: 특정 이벤트에서 데이터를 자동으로 다시 가져오는 기능을 제공.

이렇게 Tanstack query의 다양한 옵션을 조합하여 사용자 경험을 극대화할 수 있는 효율적인 캐시 관리 전략을 구현할 수 있다.

profile
모든 건 기세다.

0개의 댓글

관련 채용 정보