무엇
QueryObserver는 한 쿼리를 구독해 그 상태 변화를 콜백으로 받는 저수준 클래스다. useQuery 같은 프레임워크 훅이 바로 이 클래스 위에 세워져 있다.
QueryClient와 옵션을 넘겨 인스턴스를 만들고, subscribe로 상태 변화를 듣는다. 옵션은 useQuery에 넘기는 것과 같은 모양이다.
const observer = new QueryObserver(queryClient, { queryKey: ['posts'] })
무슨 일이 일어나나
TanStack Query의 코어는 프레임워크를 모른다. 캐시(QueryClient)와 옵저버가 실제 일을 하고, React 어댑터는 그 위에 얇게 얹힌 층이다. useQuery를 부르면 내부에서 QueryObserver를 하나 만들어 구독하고, 옵저버가 상태 변화를 알릴 때마다 리렌더를 일으킨다.
옵저버 하나는 쿼리 하나를 본다. 캐시에서 데이터·에러·상태를 읽어 현재 결과를 만들고, staleTime·refetchInterval 같은 옵션에 따라 언제 다시 가져올지도 관리한다. 옵션을 바꾸면 보던 쿼리를 다른 쿼리로 갈아탈 수도 있다. React 없이 이 흐름을 직접 다루고 싶을 때 쓰는 게 옵저버다.
사용법
옵저버를 만들고 subscribe로 결과를 듣는다. subscribe는 구독 해제 함수를 돌려준다.
import { QueryClient, QueryObserver } from '@tanstack/query-core'
const queryClient = new QueryClient()
const observer = new QueryObserver(queryClient, {
queryKey: ['posts'],
queryFn: fetchPosts,
})
const unsubscribe = observer.subscribe((result) => {
console.log(result.data, result.status)
})
// 다 쓰면 구독을 해제한다.
unsubscribe()
현재 결과가 바로 필요하면 observer.getCurrentResult()로 읽고, 보는 쿼리를 바꾸려면 observer.setOptions(...)로 옵션을 교체한다. 완전히 정리할 때는 observer.destroy()를 부른다.
실무 예시
React 밖에서, 예컨대 상태 관리 라이브러리나 커스텀 통합 안에서 쿼리 캐시를 구독하는 경우다. 훅을 쓸 수 없는 환경에서 옵저버로 직접 데이터 변화를 흘려보낸다.
import { QueryObserver } from '@tanstack/query-core'
// 훅을 못 쓰는 곳에서 쿼리 결과를 외부 스토어로 넘긴다.
export function bridgeToStore(queryClient, store) {
const observer = new QueryObserver(queryClient, {
queryKey: ['session'],
queryFn: fetchSession,
})
return observer.subscribe((result) => {
store.setSession(result.data)
})
}
왜 중요한가
QueryObserver는 대부분의 개발자가 직접 부를 일이 없는 클래스다 — useQuery가 내부에서 대신 써 주기 때문이다. 그래도 알아 둘 가치는 있다. 훅이 어떻게 동작하는지가 여기서 드러난다. 옵저버가 캐시를 구독하고 결과를 만들어 리렌더를 부른다는 그림을 잡으면, staleTime이나 리페치 타이밍 같은 것들이 왜 그렇게 도는지 이해하기 쉬워진다. 직접 쓰는 건 프레임워크 어댑터나 커스텀 통합을 만드는 드문 경우로 한정된다.
Reference