React - Tanstack Query 기초

Hunjin·2025년 12월 13일

Tanstack Query 기초에 대해서 알아보겠습니다.

Tanstack Query는 상태 관리를 하는 라이브러리중 하나입니다.

공식문서를 보면 제일 먼저 보이는 문구입니다.
무슨 뜻일까요?

"TS/JS, React, Vue, Solid, Svelte, Angular 애플리케이션을 위한 강력한 비동기 상태 관리, 서버 상태 유틸리티 및 데이터 가져오기(Data Fetching) 도구"

라고 소개를 하고 있습니다.


왜 사용할까?

우리가 클라이언트를 구축할 때의 상태는 크게 2가지로 나눌 수 있습니다.

  1. 클라이언트 상태
  2. 서버 상태

기존의 상태 관리 도구는 '클라이언트 상태'를 관리하기에는 좋지만
'서버 상태'를 관리할때는 문제가 많았습니다.

그럼 어떤 문제가 있었는지?

tanstack query를 사용하지 않으면 개발자는 로딩 상태, 에러 상태, 데이터 저장을 하나하나 다 직접 구현해야 합니다.

const [data, setData] = useState(null); 
const [loading, setLoading] = useState(true); 
const [error, setError] = useState(null); 

useEffect(() => { 
	fetch('/api/posts') 
	.then(res => res.json()) .then(setData) 
	.catch(setError) 
	.finally(() => setLoading(false)); 
 }, []);
  • 코드가 길다: 단순히 데이터 하나 가져오는데 useState useEffect try-catch 등 보일러플레이트가 너무 깁니다.
  • 캐싱이 없다: 페이지를 나갔다가 다시 들어오면 서버에 다시 요청합니다.
  • 최신화가 안 된다: 내가 보고 있는 사이에 서버 데이터가 바뀌어도, 새로고침을 안 하면 알 수 없습니다.
  • 중복 요청: 여러 컴포넌트에서 같은 데이터가 필요하면, 컴포넌트마다 요청을 따로 보내게 됩니다.

그럼 Tanstack Query를 사용했을때 어떤 장점이 있는지?

  • 로딩/에러/성공 상태 자동 관리
  • 캐싱 및 중복 요청 방지
  • 데이터 신선도(staleTime), 자동 재요청 옵션 지원
  • 코드가 간결 및 유지보수 쉬워짐


useQuery

  • 가장 기본적인 쿼리 훅으로, 컴포넌트에서 데이터를 가져올 때 사용합니다.
const ReactQueryPage = () => {

    const fetchPosts = () => {
        return axios.get('http://localhost:3004/posts')
    }

    const {data, isLoading, error} = useQuery({
        queryKey: ['posts'], 
        queryFn: fetchPosts,
        retry: 3,
        select: (data) => {
            return data.data
        } 
    })

해당 코드의 동작 결과는

이렇게 나옵니다 짜잔~

동작 흐름을 요약하면

  1. 마운트 : 페이지 컴포넌트가 화면에 나타난다.
  2. 데이터 확인 : [’posts’]라는 키로 저장된 캐시 데이터가 있는지 확인한다.
  3. 요청 시작 : isLoading이 true가 되고, 화면에는 “로딩 중” 이 뜨며 동시에 fetchPosts가 실행된다.
  4. 데이터 도착
    • 서버에서 응답이 오면 select 함수가 실행되고 data를 추출한다.
    • isLoading은 false가 되고, data 변수에 실제 게시글 목록이 담긴다.
    • 컴포넌트가 다시 렌더링되면서 데이터 리스트 목록이 화면에 나타난다.
  5. 캐싱 : 가져온 데이터는 메모리에 캐싱된다.

만약 Tanstack Query를 사용하지 않은 상태에서 다른 페이지로 이동을 하고 다시 원래 페이지로 돌아온다고 가정을 해봅시다.

그럼 서버에서 데이터를 가져와야 합니다.
위와 같은 동작이 반복된다면 어떤 문제가 있을까요?

  • 같은 데이터를 계속 요청
  • 불필요한 네트워크 트래픽 발생

등과 같은 문제점이 발생할 수 있습니다.

그래서 등장한 Tanstack Query의 캐싱

캐싱은 한 번 가져온 데이터를 메모리에 저장해 두었다가,
같은 데이터가 필요할 때 서버에 다시 요청하지 않고 저장된 값을 재사용하는 것입니다.

Q : 그렇다고 캐시가 있으면 API 호출을 아예 안 하는가?
A : 아닙니다. 캐시가 있어도 API를 호출합니다.

하지만 로딩 대신에 캐시된 데이터를 미리 보여주며 뒤에서는 API 호출 작업을 하고

호출이 완료되면 다시 업데이트를 진행합니다.


TanStack Query Lifecycle

1. Fetching : API 호출 중

  • 현재 서버에 요청 중인 상태
  • isLoading: true 또는 isFetching: true
  • 이 상태에서는 서버 요청이 진행 중

2. Fresh : 신선한 데이터

  • 데이터가 최신 상태
  • staleTime 내에 있는 데이터
  • 이 상태에서는 자동으로 fetch하지 않음
  • 캐시에서 즉시 반환

3. Stale : 오래된 데이터

  • 데이터의 유통기한이 지난 상태
  • staleTime이 지나면 Stale로 변경
  • 이 상태에서 컴포넌트가 마운트되면 자동으로 fetch
  • 백그라운드에서 새 데이터를 가져옴

4. Inactive : 비활성 상태

  • 현재 어떤 컴포넌트에서도 사용하지 않는 상태
  • 캐시에는 남아있지만 사용되지 않음
  • gcTime 동안 메모리에 유지됨
  • DevTools에서 "Inactive 1"로 표시

5. Deleted : 삭제됨

  • gcTime이 지나서 가비지 컬렉션된 상태
  • 메모리에서 완전히 삭제됨
  • 다시 사용하려면 처음부터 fetch해야 함

staleTime vs gcTime

staleTimegcTime은 목적과 작동 시점이 다릅니다

  • staleTime : 상한 상태
  • gcTime : 쓰레기통 시간

staleTime

  • 설정된 시간이 지나지 않았다면 사용자가 새로고침을 하거나 다시 접속해도 서버 요청을 안 보냅니다.
  • 설정된 시간이 지났다면 데이터를 캐시 데이터를 보여주고 뒤에서 서버에 다시 요청을 해서 데이터를 받아옵니다.

gcTime

  • 이 데이터가 화면에서 사라지는 순간부터 타이머가 돌아갑니다.
  • 타이머가 끝나기 전에 다시 들어오면 데이터가 살아있으니 바로 보여줍니다.
  • 타이머가 끝나면 메모리에서 완전히 삭제되며 다시 들어오면 다시 데이터를 받아옵니다.

useMutation

Tanstack Query는 데이터 변경 작업(생성, 수정, 삭제 등)을 위한 useMutation 훅을 제공합니다.

이를 통해, 데이터 변경 작업을 처리하고 다양한 성공, 실패, 로딩 등의 상태를 얻을 수 있습니다.

그리고 요청 실패 시의 자동 재시도나 낙관적 업데이트 같은 고급 기능도 쉽게 처리할 수 있습니다.

useQuery는 가져오기, useMutation는 보내기에

export const useCreatePost = () => {
  // 1. 쿼리 클라이언트 가져오기
  const queryClient = useQueryClient() 

  return useMutation({
    // 2. 실제로 수행할 작업 (서버 요청)
    mutationFn: (member: string) => {
      return axios.post('http://localhost:3004/posts', { member })
    },
    
    // 3. 성공 시 실행할 로직 (Side Effect)
    onSuccess: () => {
      // 4. 'posts'라는 키를 가진 쿼리를 무효화(invalidate) 시킴
      queryClient.invalidateQueries({ queryKey: ['posts'] })
    },
  })
}

useQueryClient()

  • 왜 필요한가요? useMutation 자체는 데이터 수정만 담당합니다. 수정이 끝난 뒤 이미 캐시 되어 있는 다른 데이터(['posts'])를 건드릴때 권한이 필요하기 때문입니다.

mutationFn

  • 역할: 실제로 서버에 변경 요청을 보내는 함수입니다.
  • 코드 해석: axios.post를 사용해 서버에 member 데이터를 담아 보냅니다.
  • 특징: 나중에 이 훅을 사용하는 컴포넌트에서 mutate("훈진")라고 호출하면, 여기서 member 변수에 "훈진"이 들어옵니다.

onSuccess : 성공시 처리

  • 역할: mutationFn이 에러 없이 성공적으로 끝났을 때만 실행되는 함수로 데이터 변경이 성공한 직후에 무엇을 할지 정의합니다.

queryClient.invalidateQueries({ queryKey: ['posts'] })

  • 진행:
    • invalidateQueries['posts']라는 이름표 붙은 데이터 상했어(Invalid). 버려~" 라고 말합니다.
    • 그러면 React Query는 useQuery를 재실행(Refetch)합니다.
    • 최신 상태(11개)로 업데이트 됩니다.
profile
프론트 개발을 해보아요👨🏻‍💻

1개의 댓글

comment-user-thumbnail
2025년 12월 14일

Tanstack Query는 최근 간단하게 사용해보기는 했지만, 글을 읽으면서 전반적으로 사용 방법과 개념들을 정리할 수 있어서 좋았습니다. 모든 기술들을 사용할 때에는 명확한 이유가 있어야 한다고 생각하는데, 기존 코드에서는 어떠한 문제점이 있었고, Tanstack Query를 사용하면 이러한 문제들에 대해서 어떤 장점을 얻을 수 있는지 알 수 있었습니다.

특히 Tanstack Query의 라이프 사이클을 기준으로 각 상태가 어떤 의미인지 설명되어 있어서 조금 더 쉽게 이해할 수 있었던 것 같습니다. 추후 Tanstack Query를 사용할 때에는 누군가, 그리고 어디선가 사용했던 코드를 보고 작성하는 것이 아니라, 해당 코드가 명확히 어떤 의미이고 어떤 역할을 하는지 인지한 후 사용하는 것이 중요할 것 같습니다.

답글 달기