본문 바로가기
이론/Frontend

TanStack-Query 효율적으로 활용하기

by 유세지 2025. 3. 4.

이 글의 코드는 Tanstack-Query V4 코드를 기준으로 작성되었습니다.

 

 

 

들어가며

TanStack Query (전 React-Query)는 클라이언트에서 서버 상태를 쉽게 관리할 수 있도록 도와주는 라이브러리입니다. 대표적으로 캐싱, 자동 업데이트, 로딩과 에러 등의 통신 상태에 관련된 추상화 된 인터페이스들을 제공하는데, 그 기능들이 굉장히 강력하고 편리해서 프론트엔드 생태계 전반적으로 널리 사용되는 인기있는 라이브러리가 되었습니다. 오늘은 이 TanStack Query가 지원하는 기능들을 통해 프로젝트에서 유용하게 활용했던 몇 가지 방법에 대해 정리하려 합니다.

 

 

쿼리 무효화 (Query Invalidation)

캐시된 데이터를 강제로 갱신하기 위해 refetch() 를 사용하는 방법도 있지만, 보통의 경우에는 invaliateQueries() 를 권장합니다. refetch는 호출 시 데이터를 강제로 다시 받아오는 작업을 수행하는 반면에, invalidateQueries는 데이터를 stale한 상태로 만듭니다. 덕분에 이후 해당 데이터가 호출되는 곳에서만 새로운 데이터를 받아오도록 처리할 수 있어, 개발자가 데이터의 흐름을 더 세밀하고 안전하게 다룰 수 있게 해줍니다.

invalidateQueries가 지원하는 몇 가지 속성을 이용하면, 데이터를 더 다양한 방법으로 다룰 수 있습니다.

 

const { data } = useQuery({
  queryKey: ['example', exampleSeq]
  queryFn: getExample,
  enabled: Boolean(exampleSeq),
})

// ...

queryClient.invalidateQueries(['example', exampleSeq])

 

위와 같이 코드가 있다고 가정해보겠습니다. 이전에 데이터를 불러왔더라도, 쿼리가 마운트되지 않은 상황에서는 위의 무효화 코드가 동작하지 않습니다. invalidateQueries의 기본 동작이 활성화 된 쿼리만 refetch를 수행하기 때문인데요, 어떤 상태의 쿼리를 다시 가져올지에 대해서는 refetchType 속성을 통해 조정할 수 있습니다.

 

queryClient.invalidateQueries({
  queryKey: ['example', exampleSeq],
  refetchType: 'active' | 'inactive' | 'all',
})

 

refetchType 속성은 아래 세 가지 값을 가집니다.

 

  1. 'active' 는 활성화 된 쿼리만 다시 가져옵니다.
    무효화에서 refetchType에 별다른 지정을 하지 않았을때 이 값을 기본값으로 가져갑니다.
  2. 'inactive' 는 백그라운드 상태의 데이터를 업데이트 할 때 사용합니다.
    현재 사용하고 있지 않은 데이터들이지만, 이전에 가져온 값들을 갱신할 필요가 있을 경우에 사용합니다.
  3. 'all' 은 쿼리의 활성화 여부에 관계없이 모든 쿼리를 다시 요청합니다.

 

queryClient.invalidateQueries({
  predicate: (query) =>
    query.queryKey[0] === 'example' && query.state.fetchStatus !== 'idle',
  refetchType: 'all',
})

 

여기서 더 세부적인 조건을 걸어주고 싶다면, predicate 속성을 이용할 수 있습니다.

 

위처럼 refetchType: all 을 통해 모든 쿼리를 다시 요청하도록 해주고, 그 중에서 fetchStatus가 idle인 쿼리만 재요청을 하도록 보내줄수도 있습니다. 이렇게 하면 쿼리의 mount 여부에 상관없이 enable: false인 쿼리는 제외하고 데이터를 다시 받아올 수 있습니다.

 

쿼리키 매칭을 통해 일괄적으로 데이터를 갱신할 필요가 있을때는 exact를 이용하면 좋습니다.

 

queryClient.invalidateQueries({
  queryKey: ['example'],
  exact: false,
})

 

이렇게 exact를 false로 설정해주면, 쿼리키가 example로 시작하는 모든 쿼리에 대해 무효화를 실행합니다.

따로 명시해주지 않으면 기본값은 true이기 때문에, 정확히 일치하는 쿼리에 대해서만 작동합니다.

 

단, 이렇게 하려면 정말 example / * 쿼리키를 가진 모든 쿼리가 갱신되어야 한다는 확신이 있을 때만 하는게 좋습니다.

 

 

쿼리 데이터 변경 (SetQueryData)

 

TanStack Query는 서버 상태를 관리하는 라이브러리지만, 어떤 작업을 할때 반드시 쿼리 데이터를 다시 받아와야만 할 필요가 없을수도 있습니다. 대표적인 예시로 낙관적 업데이트를 진행할때가 있는데요. 응답에 관계없이 사용자의 인터렉션에 따라 UI 업데이트를 진행해야 할 때 유용합니다.

 

const mutation = useMutation(updateExampleAPI, {
  onMutate: async (newData) => {
    const previousData = queryClient.getQueryData(['example']);

    queryClient.setQueryData(['example'], (oldData) => {
      if (!oldData) return newData;
      return { ...oldData, ...newData };
    });

    return { previousData };
  },
});

 

onMutate 에서 getQueryData 에 쿼리키를 인자로 넘겨 기존 데이터를 받아오고 리턴해줌과 동시에, setQueryData 를 통해 데이터의 변경을 적용해줍니다. 다시 쿼리 요청(get)을 보내지 않고, 기존의 캐시 상태를 업데이트 해주는 방법입니다.

 

setQueryData는 로컬에 있는 캐시 데이터를 변경하는 메서드이기 때문에, 동기적으로 작동하여 순서만 맞춘다면 곧바로 적용해도 무방합니다.

 

const mutation = useMutation(updateExampleAPI, {
  onMutate: async (newData) => {

  // ...

  onError: (error, newData, context) => {
    queryClient.setQueryData(['example'], context?.previousData);
  },
});

 

혹시나 통신에 실패했을경우, onError 에서 유지하고 있던 previousData를 사용해 기존 값으로 되돌릴 수 있습니다. 불필요한 네트워크 자원의 낭비를 줄이고, UI에는 빠르게 반영이 필요할 때 사용하기 좋습니다.

 

  const mutation = useMutation(updateExampleAPI, {

    // ...

    onSettled: () => {
      queryClient.invalidateQueries(['example']);
    }
  }

 

혹시나 정확한 데이터 싱크가 필요하다면, onSettled 에서 기존의 쿼리를 무효화 시켜주는것으로 쿼리 요청을 다시 보내줄 수 있습니다. 이때도 위에서 언급했던 속성들을 이용하면 더 세밀한 데이터 조작이 가능해집니다.

 

 

다만 서비스의 특성에 따라 데이터를 컨트롤하는 적절한 방법은 달라질 수 있으므로, 효율적으로 사용하기 이전에 지금 사용하는 방법이 적절한 방향인지 꼭 확인하고 적용하는 것이 중요합니다.

 

참고 문서

TanStack Query v4 Docs - Query Filters

반응형

댓글