728x90
반응형
TanStack Query 시작하기 , Tanstack Query를 사용하는 이유를 설명해 주세요

왜 TanStack Query를 사용해야 하는지?
프론트엔드 개발에서 서버 데이터와의 동기화, 캐싱, 에러 처리, 로딩 상태 관리… 이 모든 것을 직접 구현하려다 보면 코드가 복잡해지고, 버그도 늘어나기 쉽습니다.
이럴때 필요한 것이 바로 TanStack Queryt(구 React Query) 입니다.
클라인언트 상태, 서버 상태 , 데이터 패칭, 캐싱, 동기화 , 리트라이 , 뮤테이션까지 한번에 해결가능한 tanStack Query 에 대해서 알아보도록 하겠습니다.
목차
- TanStack Query란?
- TanStack Query가 필요한 이유: 전통적 상태 관리와의 비교
- 실전 예제로 배우는 TanStack Query
- 주요 기능 및 옵션 설명
- 트러블슈팅 & 실전 팁
- 베스트 프랙티스 vs. 안티패턴
- 용어 설명
1. TanStack Query란?
TanStack Query는 서버 상태(server state) 관리에 특화된 데이터 페칭 라이브러리입니다.
React, Vue, Svelte 등 다양한 프레임워크에서 사용 가능하며,
특히 비동기 데이터 요청과 캐싱, 동기화, 자동 재요청 등 서버 데이터 관리의 복잡성을 획기적으로 줄여줍니다.
서버 상태
- DB나 외부 API 등 서버에서 관리하는 데이터
- 여러 사용자가 공유하는 데이터
- 예시: 사용자 프로필 정보, 게시글 데이터, 상품 정보, 결제 내역 등
클라이언트 상태
- UI의 토글 상태, 입력값 등 프론트엔드에서만 의미 있는 데이터
- 특정 사용자의 브라우저/기기에만 존재하는 로컬 데이터
- 예시: 모달 창 열림/닫힘 상태, 폼 입력값, 테마 설정, 페이지 스크롤 위치 등
2. TanStack Query가 필요한 이유: 전통적 상태 관리와의 비교
기존 방식의 한계
예시: useState + useEffect로 데이터 요청
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));
}, []);
문제점
- 로딩/에러 상태 직접 관리 → 코드 중복
- 같은 데이터 여러 컴포넌트에서 요청 시 중복 네트워크 발생
- 데이터 갱신(동기화) 로직 직접 구현 필요
- 캐싱, 백그라운드 업데이트, 자동 재시도 등 고급 기능 직접 구현해야 함
TanStack Query 도입 시
import { useQuery } from '@tanstack/react-query';
const { data, isLoading, isError, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(res => res.json()),
});
장점
- 로딩/에러/성공 상태 자동 관리
- 캐싱 및 중복 요청 방지
- 데이터 신선도(staleTime), 자동 재요청 등 고급 옵션 지원
- 코드가 간결해지고, 유지보수가 쉬워짐
Redux 등 전역 상태 관리와의 차이
TanStack | QueryRedux/Context | |
목적 | 서버 상태(비동기 데이터) 관리 | 클라이언트 상태(동기 데이터) 관리 |
코드량 | 적음 | 많음(보일러플레이트) |
비동기 지원 | 내장 (자동 상태관리) | 미들웨어 필요 (redux-thunk 등) |
캐싱/동기화 | 내장 | 직접 구현 필요 |
SSR 지원 | 매우 우수 | 직접 구현 필요 |
3. 실전 예제로 배우는 TanStack Query
3.1 기본 데이터 요청
import { useQuery } from '@tanstack/react-query';
function PostList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const res = await fetch('/api/posts');
if (!res.ok) throw new Error('데이터 요청 실패');
return res.json();
},
staleTime: 60 * 1000, // 1분간 데이터 신선하게 유지
retry: 2, // 실패 시 2번까지 재시도
});
if (isLoading) return <div>로딩 중...</div>;
if (isError) return <div>에러: {error.message}</div>;
return (
<ul>
{data.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
- useQuery는 데이터 요청, 캐싱, 상태 관리까지 한 번에!
- queryKey는 캐시의 고유 식별자(동일 키면 중복 요청 방지)
- staleTime으로 데이터 신선도 제어
- retry로 네트워크 이슈에 유연하게 대응
3.2 데이터 생성/수정/삭제 (Mutation)
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newPost) =>
fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
}).then(res => res.json()),
onSuccess: () => {
// 성공 시 posts 쿼리 무효화(자동 재요청)
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
// ...
}
- useMutation은 서버 데이터 변경(POST/PUT/DELETE)에 사용
- onSuccess에서 관련 쿼리 무효화로 데이터 일관성 유지
3.3 페이지네이션/무한 스크롤
import { useInfiniteQuery } from '@tanstack/react-query';
function InfinitePosts() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 1 }) =>
fetch(`/api/posts?page=${pageParam}`).then(res => res.json()),
getNextPageParam: (lastPage, allPages) =>
lastPage.hasNext ? allPages.length + 1 : undefined,
});
// ... 스크롤 감지 후 fetchNextPage 호출
}
- useInfiniteQuery는 무한 스크롤, 페이지네이션에 최적화
- getNextPageParam으로 다음 페이지 조건 제어
4. 주요 기능 및 옵션 설명
핵심 옵션 한눈에 보기
옵션명 | 설명 | 예시값 |
queryKey | 쿼리 식별자 (배열 권장) | ['posts', page] |
queryFn | 데이터를 불러오는 함수 | () => fetch('/api/posts') |
staleTime | 데이터 신선도 유지 시간(ms) | 60 * 1000 (1분) |
cacheTime | 메모리 캐시 유지 시간(ms) | 5 * 60 * 1000 (5분) |
refetchOnWindowFocus | 윈도우 포커스 시 자동 재요청 여부 | true/false |
enabled | 조건부 쿼리 활성화 | !!userId |
retry | 실패 시 재시도 횟수 | 2, 3, (error) => ... |
onSuccess | 성공 시 콜백 | (data) => ... |
onError | 에러 시 콜백 | (error) => ... |
5. 트러블슈팅 & 실전 팁
쿼리 키 관리가 꼬일 때
- 항상 배열 형태로, 계층적으로 관리
- 예시: ['users', userId, 'posts']
데이터가 자동으로 갱신되지 않을 때
- mutation 후 invalidateQueries로 쿼리 무효화
- enabled 옵션 활용: userId 등 필수 값이 없을 때 쿼리 비활성화
불필요한 네트워크 요청 방지
- staleTime, cacheTime을 상황에 맞게 조절
- 자주 변하지 않는 데이터는 staleTime을 길게
에러 핸들링
- onError 콜백에서 toast 등 사용자 알림 처리
- 글로벌 에러 핸들링은 QueryClient의 queryCache 옵션 활용
6. 베스트 프랙티스 vs. 안티패턴
데이터 페칭 및 캐싱
베스트 프랙티스 | 안티패턴/주의점 |
일관된 queryKey 구조 사용하기 ([entity, id] 형태 권장) | 복잡하거나 일관성 없는 queryKey 구조 사용 |
관심사 분리를 위해 커스텀 훅으로 쿼리 로직 캡슐화 | 컴포넌트 내에 직접 복잡한 쿼리 로직 작성 |
타입 안전성을 위해 TypeScript 사용 | 타입 정의 없이 사용하여 런타임 에러 위험 증가 |
필요한 경우에만 enabled 옵션 사용 | 조건부 로직을 위해 컴포넌트 렌더링 조건 사용 |
전역 설정을 위해 QueryClient 기본값 구성 | 모든 쿼리마다 반복적인 옵션 설정 |
상태 관리
베스트 프랙티스 | 안티패턴/주의점 |
isLoading과 isFetching 상태 구분해서 사용 | 두 상태를 혼동하여 잘못된 로딩 UI 표시 |
에러 처리를 위해 isError와 error 활용 | queryFn 내부에서 에러를 무시하거나 숨기기 |
select 옵션으로 필요한 데이터만 변환/추출 | 불필요한 리렌더링을 유발하는 컴포넌트 내 데이터 변환 |
참조 안정성을 위해 useCallback으로 queryFn 감싸기(fn by fn) | 렌더링마다 새로운 함수 참조 생성 (fn by fn) |
초기 데이터를 위해 initialData 또는 placeholderData 활용 | 로딩 상태 처리를 위한 복잡한 조건부 렌더링 |
뮤테이션 및 데이터 업데이트
베스트 프랙티스 | 안티패턴/주의점 |
낙관적 업데이트를 위해 onMutate와 context 활용 | 뮤테이션 후 수동으로 쿼리 리페칭만 의존 |
관련 쿼리 무효화를 위해 invalidateQueries 사용 | 단일 쿼리만 무효화하여 관련 데이터 불일치 발생 |
쿼리 데이터 직접 수정을 위해 setQueryData 활용 | 서버 상태와 클라이언트 상태 불일치 방치 |
에러 발생 시 이전 상태로 복원하기 위해 onError 활용 | 낙관적 업데이트 후 에러 처리 누락 |
뮤테이션 성공 후 적절한 사용자 피드백 제공 | 뮤테이션 상태 변화를 사용자에게 알리지 않음 |
네트워크 및 성능 최적화
베스트 프랙티스 | 안티패턴/주의점 |
적절한 staleTime 설정으로 불필요한 리패칭 방지 | 모든 쿼리에 기본 staleTime: 0 사용 |
데이터 사용 패턴에 따라 cacheTime 조정 | 너무 짧은 cacheTime으로 캐시 이점 감소 |
네트워크 상태에 따른 재시도 전략 구성 | 무한 재시도 설정 또는 재시도 없음 |
필요한 경우에만 refetchOnReconnect 활용 | 모든 쿼리에 불필요한 리커넥트 리패칭 활성화 |
인증 토큰 관리를 위한 QueryClient 기본 설정 활용 | 각 쿼리마다 인증 로직 중복 구현 |
7. 용어 설명
- 서버 상태(Server State): 서버에서 관리되고 여러 사용자가 공유하는 데이터
- 클라이언트 상태(Client State): UI 토글, 입력값 등 프론트엔드에서만 의미 있는 상태
- 쿼리 키(Query Key): 쿼리의 고유 식별자, 캐싱 및 중복 요청 방지에 사용
- 캐싱(Caching): 이미 불러온 데이터를 메모리에 저장해 재사용
- 낙관적 업데이트(Optimistic Update): 서버 응답 전 UI를 먼저 변경, 실패 시 롤백
- SSR(Server Side Rendering): 서버에서 HTML을 미리 렌더링해 SEO 및 초기 속도 개선
https://tanstack.com/query/latest
TanStack Query
Powerful asynchronous state management, server-state utilities and data fetching. Fetch, cache, update, and wrangle all forms of async data in your TS/JS, React, Vue, Solid, Svelte & Angular applications all without touching any "global state"
tanstack.com
728x90
반응형
'React' 카테고리의 다른 글
React에서 데이터 로딩 상태 관리: useEffect vs Suspense (0) | 2025.04.15 |
---|---|
useEffect와 useLayoutEffect의 차이점에 대해서 설명해주세요. (0) | 2025.03.19 |
리액트 폼 다루기: Controlled Component와 Uncontrolled Component 가이드 (0) | 2025.02.26 |
<React Recoil> - 리액트 상태관리 리코일 / atoms,hooks (0) | 2023.09.26 |
리액트(React) - useState 내부 구현과 동작 원리(feat : 클로저) (0) | 2023.09.25 |