| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
- 자바문법
- 자바
- junit5
- IntelliJ
- 배열
- ArrayList
- 문자열
- js
- SpringBoot
- Visual Studio Code
- Array
- input
- 테스트자동화
- string
- list
- junit
- 인텔리제이
- 정규식
- 자바스크립트
- CSS
- javascript
- HashMap
- 단위테스트
- vscode
- Java
- 스프링부트
- java테스트
- Eclipse
- html
- math
- Today
- Total
어제 오늘 내일
[React] TanStack Query 사용법: 서버 상태 관리 제대로 하기 본문
1. 들어가며: 어떤 문제를 해결하는가
React에서 서버 데이터를 가져오는 가장 흔한 방법은 useEffect와 useState를 조합하는 것입니다.
function TodoList() {
const [todos, setTodos] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
fetch('/api/todos')
.then((res) => res.json())
.then((data) => setTodos(data))
.catch((err) => setError(err))
.finally(() => setLoading(false))
}, [])
// ...
}간단해 보이지만, 실제 프로덕션에서는 금방 문제가 드러납니다.
- 같은 데이터를 여러 컴포넌트에서 각자 fetch하면서 중복 요청이 발생
- 사용자가 다른 탭에 갔다 돌아왔을 때 최신 데이터로 갱신해야 하는데 그 로직이 없음
- 요청이 실패했을 때 재시도하는 로직을 직접 구현해야 함
- 데이터를 수정한 뒤 화면을 어떻게 갱신할지 캐시 무효화 로직을 매번 작성해야 함
loading,error,data세 가지 상태를 관리하는 보일러플레이트가 컴포넌트마다 반복됨
TanStack Query(구 React Query)는 이런 "서버 상태(server state)"를 다루는 문제들을 표준화된 방식으로 해결해주는 라이브러리입니다. 클라이언트 상태(useState, Zustand 등으로 관리하는 UI 상태)와 서버 상태는 성격이 달라서, 별도의 도구로 다루는 게 낫다는 철학에서 출발합니다.
2. 설치 및 설정
npm install @tanstack/react-query앱 최상단에서 QueryClient를 만들고 QueryClientProvider로 감싸줘야 합니다.
// main.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
const queryClient = new QueryClient()
function Root() {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
)
}3. 기본 사용법: useQuery로 데이터 가져오기
useQuery는 두 가지가 핵심입니다. 캐시를 구분하는 queryKey와, 실제로 데이터를 가져오는 queryFn입니다.
import { useQuery } from '@tanstack/react-query'
function TodoList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then((res) => res.json()),
})
if (isLoading) return <p>불러오는 중...</p>
if (isError) return <p>에러: {error.message}</p>
return (
<ul>
{data.map((todo) => <li key={todo.id}>{todo.title}</li>)}
</ul>
)
}useEffect 버전과 비교하면 loading/error/data 상태를 직접 관리하는 코드가 사라지고, "무엇을(queryKey) 어떻게(queryFn) 가져올지"만 선언하면 된다는 차이가 눈에 띕니다.
4. queryKey와 캐싱의 관계
queryKey는 단순한 이름표가 아니라 캐시를 구분하는 기준입니다. 같은 키를 가진 쿼리는 여러 컴포넌트에서 동시에 호출해도 실제 네트워크 요청은 한 번만 일어나고, 결과를 공유해서 사용합니다.
파라미터가 있는 요청은 배열 뒤에 값을 추가해서 키를 구분합니다.
function TodoDetail({ todoId }) {
const { data } = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetch(`/api/todos/${todoId}`).then((res) => res.json()),
})
return <p>{data?.title}</p>
}todoId가 바뀌면 TanStack Query가 자동으로 이를 감지해서 새로운 캐시 키로 다시 요청합니다. 의존성 배열을 따로 관리할 필요가 없습니다.
5. 자동 갱신 동작 이해하기
TanStack Query는 기본적으로 몇 가지 상황에서 데이터를 자동으로 다시 가져옵니다(refetch).
- 브라우저 창에 다시 포커스가 돌아왔을 때 (
refetchOnWindowFocus) - 네트워크가 끊겼다가 다시 연결됐을 때 (
refetchOnReconnect) - 컴포넌트가 다시 마운트됐을 때, 캐시가 오래되었다면
이 동작이 필요 없는 쿼리라면 옵션으로 끌 수 있습니다.
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 1000 * 60 * 5, // 5분간은 신선한 데이터로 간주, refetch 안 함
refetchOnWindowFocus: false, // 창 포커스 시 자동 refetch 끄기
})staleTime은 얼마나 자주 바뀌는 데이터인지에 따라 조절하면 됩니다. 자주 바뀌지 않는 설정값 같은 데이터는 길게, 실시간성이 중요한 데이터는 짧게 잡는 식입니다.
6. useMutation으로 데이터 변경하기
조회가 아닌 생성·수정·삭제 같은 작업에는 useMutation을 사용합니다.
import { useMutation, useQueryClient } from '@tanstack/react-query'
function AddTodoForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (newTodo) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
}).then((res) => res.json()),
onSuccess: () => {
// 'todos' 쿼리를 오래된 것으로 표시 → 자동으로 최신 데이터 다시 조회
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<button onClick={() => mutation.mutate({ title: '새 할 일' })} disabled={mutation.isPending}>
{mutation.isPending ? '추가하는 중...' : '할 일 추가'}
</button>
)
}onSuccess에서 invalidateQueries를 호출하는 패턴이 핵심입니다. 어떤 데이터가 바뀌었는지 직접 상태를 갱신하는 대신, "이 키에 해당하는 캐시는 이제 오래됐다"고 표시만 하면 TanStack Query가 알아서 최신 데이터를 다시 불러와 화면을 갱신해줍니다.
7. 다른 방식과 비교
| 항목 | TanStack Query | useEffect + useState | Redux/Zustand로 서버 상태 관리 |
| 캐싱 | 자동 (queryKey 기준) | 직접 구현 필요 | 직접 구현 필요 |
| 중복 요청 방지 | 자동 | 직접 구현 필요 | 직접 구현 필요 |
| 재검증/재시도 | 내장 옵션 제공 | 직접 구현 필요 | 직접 구현 필요 |
| 로딩/에러 상태 | 자동 제공 | 직접 관리 | 직접 관리 |
| 적합한 대상 | 서버에서 오는 비동기 데이터 | 간단한 일회성 요청 | UI/클라이언트 상태 |
중요한 포인트는 TanStack Query가 Redux나 Zustand를 대체하는 게 아니라는 점입니다. 서버에서 가져오는 데이터는 TanStack Query가, 모달 열림 여부나 폼 입력값 같은 순수 클라이언트 상태는 useState나 Zustand가 담당하는 식으로 역할을 나누는 것이 일반적인 조합입니다.
8. 마무리
TanStack Query는 "서버 상태는 클라이언트 상태와 다르게 다뤄야 한다"는 관점을 코드로 구현한 라이브러리입니다. loading/error/data 삼종세트를 매번 손으로 작성하던 코드를 걷어내고, 캐싱과 재검증, 중복 요청 방지 같은 까다로운 문제들을 라이브러리에 위임할 수 있습니다. API를 다루는 컴포넌트가 두 개 이상만 되어도 도입 효과를 바로 체감할 수 있을 만큼, React 프로젝트에서 사실상 표준으로 자리 잡은 도구입니다.
'IT > React' 카테고리의 다른 글
| [ React ] Zustand 사용법 : React 상태 관리 가볍게 시작하기 (1) | 2026.08.08 |
|---|---|
| clsx로 React 조건부 클래스 깔끔하게 관리하기 (0) | 2026.08.07 |
| tailwind-merge로 Tailwind 클래스 충돌 깔끔하게 해결하기 (0) | 2026.08.07 |
| React에서 Tailwind CSS 제대로 활용하기 (설치, 활용) (0) | 2026.08.07 |
| [ React ] useReducer 사용법: 복잡한 상태 로직을 체계적으로 관리하기 (0) | 2026.08.07 |