TanStack Query
TanStack Query
서버 상태 관리 라이브러리. 캐싱, 동기화, 업데이트 자동화.
TanStack Query
서버 상태 관리 라이브러리. 캐싱, 동기화, 업데이트 자동화.
TanStack Query(구 React Query)는 서버 상태(server state)를 관리하는 강력한 비동기 데이터 페칭 라이브러리입니다. 캐싱, 백그라운드 업데이트, 중복 요청 제거, 낙관적 업데이트 등 복잡한 데이터 동기화 로직을 선언적으로 처리합니다.
핵심 개념으로 Query(읽기)와 Mutation(쓰기)이 있습니다. useQuery는 GET 요청에, useMutation은 POST/PUT/DELETE 요청에 사용합니다. queryKey로 캐시를 식별하고, staleTime과 cacheTime으로 캐시 전략을 세밀하게 제어합니다.
TanStack Query는 React, Vue, Solid, Svelte 등 다양한 프레임워크를 지원합니다. DevTools를 통해 캐시 상태를 시각적으로 확인할 수 있어 디버깅이 용이하며, TypeScript와의 타입 추론도 뛰어납니다.
SWR과 비교하면 TanStack Query가 더 많은 기능을 제공합니다. Infinite Query, Prefetching, Placeholder Data, Query Invalidation 등 복잡한 시나리오를 처리할 수 있어 대규모 애플리케이션에 적합합니다.
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
// Provider 설정
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5분간 fresh
retry: 3,
refetchOnWindowFocus: true,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
// 기본 Query
function TodoList() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(r => r.json()),
staleTime: 1000 * 60, // 1분
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<ul>
{data.map(todo => <TodoItem key={todo.id} {...todo} />)}
</ul>
);
}
// Mutation과 캐시 무효화
function CreateTodo() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newTodo: { title: string }) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
}).then(r => r.json()),
// 낙관적 업데이트
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previousTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (old: Todo[]) => [
...old,
{ id: Date.now(), ...newTodo, completed: false }
]);
return { previousTodos };
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context?.previousTodos);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
return (
<button
onClick={() => mutation.mutate({ title: '새 할일' })}
disabled={mutation.isPending}
>
{mutation.isPending ? '추가 중...' : '추가'}
</button>
);
}
// 무한 스크롤 (Infinite Query)
function InfinitePostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) =>
fetch(`/api/posts?cursor=${pageParam}`).then(r => r.json()),
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
return (
<div>
{data?.pages.map((page, i) => (
<React.Fragment key={i}>
{page.posts.map(post => <Post key={post.id} {...post} />)}
</React.Fragment>
))}
<button onClick={() => fetchNextPage()} disabled={!hasNextPage}>
{isFetchingNextPage ? '로딩...' : '더 보기'}
</button>
</div>
);
}
기술 미팅에서:
"전역 상태로 서버 데이터 관리하면 동기화 지옥이에요. TanStack Query 쓰면 캐싱, 리페칭, 로딩 상태 다 알아서 처리되고, DevTools로 캐시도 바로 확인할 수 있어요."
기술 면접에서:
"staleTime과 cacheTime의 차이는요?" - "staleTime은 데이터가 fresh로 간주되는 시간이고, cacheTime은 비활성 쿼리가 메모리에 유지되는 시간입니다. stale이면 백그라운드 리페칭이 일어납니다."
코드 리뷰에서:
"mutation 성공 후 관련 쿼리 invalidate 안 하고 있네요. invalidateQueries로 목록 갱신해야 추가된 항목이 바로 보여요. 아니면 setQueryData로 직접 캐시 업데이트하세요."