TypeScript

[React Query] QueryClient : 효율적인 상태관리와 데이터 캐싱

코샵 2024. 12. 19. 13:04
반응형

소개

React Query는 React 애플리케이션에서 서버 상태를 관리하기 위한 강력한 도구입니다. 이번 글에서는 QueryClient의 핵심 기능과 활용 방법을 자세히 알아보겠습니다.

설치

# npm을 사용하는 경우
npm install @tanstack/react-query

# yarn을 사용하는 경우
yarn add @tanstack/react-query

# pnpm을 사용하는 경우
pnpm add @tanstack/react-query

QueryClient 기본 설정

import { QueryClient } from '@tanstack/react-query';

// 기본 QueryClient 생성
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5분
      gcTime : 10 * 60 * 1000, // 10분 (v5는 gcTime, v4는 cacheTime)
      retry: 3,
      refetchOnWindowFocus: true,
    },
  },
});

기본 옵션 설명

옵션 설명 기본값
staleTime 데이터가 신선하다고 간주되는 시간 0
cacheTime 데이터가 캐시에 유지되는 시간 5분
retry 실패 시 재시도 횟수 3
refetchOnWindowFocus 창 포커스 시 재조회 여부 true

 

예시

기본적인 쿼리 설정

// api/userApi.ts
interface User {
  id: number;
  name: string;
  email: string;
}

// 쿼리 키 타입 정의
export const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (filters: string) => [...userKeys.lists(), { filters }] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: number) => [...userKeys.details(), id] as const,
};

// hooks/useUsers.ts
export const useUsers = (filters?: string) => {
  return useQuery({
    queryKey: userKeys.list(filters),
    queryFn: () => fetchUsers(filters),
    select: (data) => {
      return data.sort((a, b) => b.id - a.id);
    },
  });
};

데이터 수동 관리

// 데이터 직접 설정
queryClient.setQueryData(userKeys.detail(1), {
  id: 1,
  name: "John",
  email: "john@example.com"
});

// 데이터 업데이트
queryClient.setQueryData(userKeys.detail(1), (old: User) => ({
  ...old,
  name: "John Doe"
}));

캐시 무효화

// 특정 쿼리 무효화
const invalidateUser = async (userId: number) => {
  await queryClient.invalidateQueries({
    queryKey: userKeys.detail(userId)
  });
};

// 전체 사용자 쿼리 무효화
const invalidateAllUsers = async () => {
  await queryClient.invalidateQueries({
    queryKey: userKeys.all
  });
};

낙관적 업데이트

const useUpdateUser = () => {
  return useMutation({
    mutationFn: updateUser,
    onMutate: async (newUser) => {
      // 이전 쿼리 취소
      await queryClient.cancelQueries({
        queryKey: userKeys.detail(newUser.id)
      });

      // 이전 데이터 저장
      const previousUser = queryClient.getQueryData(userKeys.detail(newUser.id));

      // 낙관적 업데이트
      queryClient.setQueryData(userKeys.detail(newUser.id), newUser);

      return { previousUser };
    },
    onError: (err, newUser, context) => {
      // 에러 발생 시 롤백
      queryClient.setQueryData(
        userKeys.detail(newUser.id),
        context.previousUser
      );
    },
    onSettled: (newUser) => {
      // 쿼리 무효화
      queryClient.invalidateQueries({
        queryKey: userKeys.detail(newUser.id)
      });
    },
  });
};

페이지네이션 처리

export const usePaginatedUsers = (page: number) => {
  return useInfiniteQuery({
    queryKey: ['users', 'infinite'],
    queryFn: ({ pageParam = 1 }) => fetchUserPage(pageParam),
    getNextPageParam: (lastPage) => lastPage.nextPage,
    getPreviousPageParam: (firstPage) => firstPage.prevPage,
    select: (data) => ({
      pages: data.pages,
      pageParams: data.pageParams,
      totalUsers: data.pages.reduce((acc, page) => acc + page.users.length, 0),
    }),
  });
};

실시간 데이터 동기화

// 웹소켓 연결과 함께 사용
useEffect(() => {
  const ws = new WebSocket('ws://api.example.com');

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);

    // 실시간 데이터로 캐시 업데이트
    queryClient.setQueryData(userKeys.detail(data.userId), (old: User) => ({
      ...old,
      ...data
    }));
  };

  return () => ws.close();
}, []);

전역 에러 처리

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onError: (error: Error) => {
        // 전역 에러 처리
        if (error.message === 'Unauthorized') {
          // 로그아웃 처리
          logout();
        }
      },
    },
  },
});

QueryClient 고급 기능

캐시 지속성

import { persistQueryClient } from '@tanstack/react-query-persist-client';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';

const persister = createSyncStoragePersister({
  storage: window.localStorage,
});

persistQueryClient({
  queryClient,
  persister,
  maxAge: 1000 * 60 * 60 * 24, // 24시간
});

커스텀 캐시 시간

// 동적 캐시 시간 설정
const useCustomQuery = (id: string) => {
  return useQuery({
    queryKey: ['custom', id],
    queryFn: () => fetchData(id),
    staleTime: id === 'important' ? Infinity : 1000 * 60,
    gcTime: id === 'important' ? Infinity : 1000 * 60 * 5,
  });
};

성능 최적화

선택적 데이터 업데이트

// 특정 조건에 맞는 데이터만 업데이트
queryClient.setQueriesData(
  { queryKey: userKeys.all, exact: false },
  (old: User[]) => old?.map(user => 
    user.id === updatedUser.id ? updatedUser : user
  )
);

데이터 프리패칭

// 데이터 미리 로드
const prefetchUser = async (userId: number) => {
  await queryClient.prefetchQuery({
    queryKey: userKeys.detail(userId),
    queryFn: () => fetchUser(userId),
  });
};

마치며

QueryClient는 React Query의 핵심 기능을 제어하는 중요한 도구입니다. 적절한 설정과 활용을 통해 효율적인 상태 관리와 최적화된 사용자 경험을 제공할 수 있습니다.