본문 바로가기

Study

[Tanstack-Query] 구체적인 쿼리키 설정으로 캐싱 최적화하기

Tanstack-query를 사용하는 큰 이유중에 하나는 캐싱 관리이다.
나는 기존에 진행하던 사이드 프로젝트에서 쿼리키를 다음과 같이 관리하고 있었다.

상단 추천 모임
하단 모임 목록 (무한스크롤)

const MEETING_QUERY_KEYS = {
  topMeetings: (category: string) => ['topMeetings', category] as const,
  meetings: (category: string) => ['meetings', category] as const,
};

 

staleTime을 1분으로 설정해두었는데, 1분 이내에 페이지를 이동해도 추천 모임은 캐싱된 데이터를 사용하기 때문에 API 호출이 안되는데,
모임 목록쪽은 계속 API가 호출되는것으로 보아 모임 목록 캐싱이 잘 되지 않는걸 알 수 있었다.

 

 

그 이유는 추천 모임과 모임 목록의 useQuery 쪽을 보면 알 수 있었다.

const useTopMeetings = (category: CategoryTitle, options = {}) => {
  return useQuery({
    queryKey: MEETING_QUERY_KEYS.topMeetings(category),
    queryFn: () => getTopMeetings(category),
    ...options,
  });
};

const useInfiniteSearchMeetings = (
  category: CategoryTitle,
  searchQueryObj: IMeetingSearchCondition,
  option = {},
) => {
  return useInfiniteQuery({
    queryKey: MEETING_QUERY_KEYS.meetings(category),
    queryFn: ({ pageParam = 0 }) =>
      getMeetings(pageParam, category, searchQueryObj),
    initialPageParam: 0,
    getNextPageParam: (lastPage) => {
      return lastPage?.nextCursor;
    },
    ...option,
  });
};

추천 모임을 불러오는 useTopMeetings 에서는 쿼리키로 MEETING_QUERY_KEYS.topMeetings(category) 을 사용하고 있고 queryFn에서도 category만 인자로 보내주어 API 요청시 category만 쿼리로 사용되고 있다.

 

하지만, 모임 목록을 불러오는 useInfiniteSearchMeetings에서는 쿼리키로 MEETING_QUERY_KEYS.meetings(category)을 사용하고 있지만, queryFn에서는 category와 searchQueryObj를 인자로 보내주고 API 요청 시 두가지 다 사용되고 있다.

캐싱이 정상적으로 작동하기 위해서는 캐싱 기준에 영향을 주는 모든 변수가 포함되어야 하는데, 나는 searchQueryObj 가 쿼리키에 사용되지 않고 있어 이러한 문제가 발생하는 것이었다.

 

위 내용은 공식문서에 잘 나와있다. (공식문서를 잘 읽자 🥲)

그래서 searchQueryObj도 쿼리키에 추가해주었다.

const getSortedSearchQuery = (
  searchQueryObj: IMeetingSearchCondition,
): IMeetingSearchCondition => ({
  ...searchQueryObj,
  skillArray: [...searchQueryObj.skillArray].sort(),
});

const MEETING_QUERY_KEYS = {
  topMeetings: (category: string) => ['topMeetings', category] as const,
  meetings: (category: string, searchQueryObj: IMeetingSearchCondition) => {
    const sortedSearchQueryObj = getSortedSearchQuery(searchQueryObj);
    return ['meetings', category, sortedSearchQueryObj] as const;
  },
  meetingId: (
    id: string,
    category: string,
    searchQueryObj: IMeetingSearchCondition,
  ) =>
    [
      ...MEETING_QUERY_KEYS.meetings(
        category,
        getSortedSearchQuery(searchQueryObj),
      ),
      id,
    ] as const,
};

const useInfiniteSearchMeetings = (
  category: CategoryTitle,
  searchQueryObj: IMeetingSearchCondition,
  option = {},
) => {
  return useInfiniteQuery({
    queryKey: MEETING_QUERY_KEYS.meetings(category, searchQueryObj),
    queryFn: ({ pageParam = 0 }) =>
      getMeetings(pageParam, category, searchQueryObj),
    initialPageParam: 0,
    getNextPageParam: (lastPage) => {
      return lastPage?.nextCursor;
    },
    ...option,
  });
};

searchQueryObj의 skillArray는 배열이기 때문에 같은 값인데 순서가 바뀌어서 들어올 경우를 고려해 정렬된 값을 쿼리에 담아주었다.

 

이제 캐싱이 잘 작동해서 1분 이내에 페이지 이동시 캐싱된 데이터가 잘 사용될까?

 

그 이유는 내가 개발중인 데빙 사이트에서는 사진과 같이 검색어, 기술스택, 정렬로 검색 쿼리를 사용하고 있는데

검색 쿼리가 바뀔때마다 useEffect로 쿼리를 지우고 API 를 refetch 해주는 로직을 따로 넣어놨었다.

  const queryClient = useMemo(() => new QueryClient(), []);

  // 필터에 따른 재검색
  useEffect(() => {
    queryClient.removeQueries({ queryKey: [MEETING_QUERY_KEYS.meetings] });
    refetch();
  }, [queryClient, searchQuery, refetch]);

 

그러다보니 페이지 이동시 저 useEffect의 콜백이 발생해 쿼리키를 잘 생성해 놓았어도 API가 refetch 되는 것이었다.

저 로직은 이제 더 이상 필요 없는 로직이다.

왜냐하면, 쿼리키만 잘 짜놓는다면 tanstack-query에서 쿼리키가 변경되면 알아서 refetch를 해주기 때문 …! 😲

 

그래서 해당 부분을 지우고 다시 확인을 해보았다.

정상적으로 데이터가 캐싱되어 더 이상 API가 호출되지 않음을 알 수 있었다.

 

깔끔한 네트워크 창

 

근데 tanstack-query에서 자동으로 refetch 해주니, 검색쿼리 변경시에 상태가 isLoading 될 때마다
스켈레톤 UI가 잠깐 나타났다가 사라지면서 UX가 좋지 않았다.

 

그래서 이전의 데이터를 유지해주는 옵션을 적용했더니, 검색 쿼리 변경시에는 스켈레톤 UI 없이 변경되어

시각적으로 피로도가 훨씬 줄어들어 UX를 개선시킬 수 있었다.

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

{
   placeholderData: keepPreviousData,
},

 

tanstack-query를 사용하면서 디테일한 쿼리키를 두어 캐싱 전략을 잘 짜놓으면 페이지 로드 시간이나 불필요한 API 호출을 줄여 네트워크 요청 최적화가 가능하기때문에 앞으로도 쿼리키 생성 시 좀 더 신경써야겠다고 느꼈다!

 

 

 

 

 

 

 

✨참고 자료✨

- https://tanstack.com/query/v4/docs/framework/react/overview