무엇
queryOptions는 한 쿼리의 키·함수·옵션을 한 객체로 묶어 돌려주는 헬퍼다. 이 객체를 useQuery, prefetchQuery, setQueryData 같은 여러 API에 그대로 넘겨 재사용한다.
같은 쿼리를 여러 곳에서 쓸 때, 키와 옵션을 매번 손으로 적으면 어긋나기 쉽다. queryOptions로 한 번 정의해 두면 정의가 한 곳에 모인다.
import { queryOptions } from '@tanstack/react-query'
function groupOptions(id: number) {
return queryOptions({
queryKey: ['groups', id],
queryFn: () => fetchGroups(id),
staleTime: 5 * 1000,
})
}
useQuery에 넘길 수 있는 건 대체로 다 넘길 수 있다. 다만 prefetchQuery 같은 곳에서는 화면 전용 옵션(예: select)이 무시되기도 한다.
무슨 일이 일어나나
queryOptions는 런타임에서는 넘긴 객체를 사실상 그대로 돌려준다. 진짜 값어치는 타입에 있다. 반환된 객체의 queryKey에는 그 쿼리가 내놓는 데이터 타입이 함께 새겨진다(태그된 키). 그래서 이 키를 getQueryData나 setQueryData에 넘기면 제네릭을 손으로 적지 않아도 데이터 타입이 자동으로 잡힌다.
const data = queryClient.getQueryData(groupOptions(42).queryKey)
// ^? Group[] | undefined ← 타입이 저절로 붙는다
즉 queryOptions는 "이 키를 쓰면 이 타입의 데이터가 나온다"는 약속을 키 자체에 붙여 두는 장치다. 이 연결이 없으면 getQueryData<Group[]>(...)처럼 매번 타입을 직접 대야 하고, 그러다 실제 데이터와 어긋나도 컴파일러가 못 잡는다.
사용법
한 번 정의해 여러 API에 넘긴다. 같은 groupOptions()가 훅에서도, 클라이언트 메서드에서도 그대로 쓰인다.
import { queryOptions } from '@tanstack/react-query'
export function groupOptions(id: number) {
return queryOptions({
queryKey: ['groups', id],
queryFn: () => fetchGroups(id),
staleTime: 5 * 1000,
})
}
// 훅에서
useQuery(groupOptions(1))
useSuspenseQuery(groupOptions(5))
useQueries({ queries: [groupOptions(1), groupOptions(2)] })
// 클라이언트 메서드에서
queryClient.prefetchQuery(groupOptions(23))
queryClient.setQueryData(groupOptions(42).queryKey, newGroups)
.queryKey만 떼어 쓰는 것도 자연스럽다. 캐시를 직접 읽고 쓸 때 이 태그된 키를 넘기면 타입이 맞춰진다.
실무 예시
라우트별로 데이터를 미리 가져오면서 화면에서도 같은 정의를 쓰는 경우다. 옵션 정의를 파일 하나로 모아 두고, 프리페치와 렌더가 모두 그 하나를 참조한다 — 키가 어긋날 일이 없다.
import { groupOptions } from './groupOptions'
// 라우트 진입 전 프리페치 (loader 등에서)
async function loader(id: number) {
await queryClient.prefetchQuery(groupOptions(id))
}
// 화면에서 같은 정의로 소비
function GroupDetail({ id }: { id: number }) {
const { data } = useQuery(groupOptions(id))
return <h1>{data?.name}</h1>
}
왜 중요한가
queryOptions는 v5에서 자리 잡은 패턴으로, 쿼리 정의를 컴포넌트 밖으로 빼 재사용할 때 사실상 표준이 됐다. 키가 흩어져 오타로 캐시가 갈라지는 문제, 그리고 getQueryData에 매번 타입을 손으로 대는 번거로움을 한 번에 없앤다. TypeScript 프로젝트에서 쿼리를 여러 곳이 공유한다면 먼저 손에 붙일 도구다. 반대로 쿼리를 한 컴포넌트 안에서만 쓴다면 굳이 거칠 필요는 없다.
Reference