QueryClient 인스턴스는 어떻게 전역으로 공유되는가

히니·2026년 5월 13일

React

목록 보기
6/6

QueryClientProvider의 역할

-> 아래 예제 코드를 보자.

                                    
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

  const queryClient = new QueryClient({                                                                                  
    defaultOptions: {                                                                                                       
      queries: {                                                    
        staleTime: 1000 * 60 * 5, // 5분
        retry: 1,
      },
    },
  });

  export function App() {
    return (
      <QueryClientProvider client={queryClient}>
        <MyApp />
      </QueryClientProvider>
    );
  }

여기서 QueryClientProvider의 역할은 무엇인가?
-> QueryClientProvider는 생성된 queryClient 인스턴스를 하위 컴포넌트 트리 전체에서 사용할 수 있도록 주입하는 역할을 한다. 이는 React의 Context API를 통해 동작한다.

만약에 실제로 QueryClientProvider 를 구현하게 된다면 실제 아래와 같은 예시일것이다.

// QueryClientProvider 내부 동작 (단순화)
  const QueryClientContext = React.createContext<QueryClient | null>(null);

  export function QueryClientProvider({ client, children }) {
    return (
      <QueryClientContext.Provider value={client}>
        {children}
      </QueryClientContext.Provider>
    );
  }

  export function useQueryClient() {
    const client = React.useContext(QueryClientContext);
    if (!client) throw new Error('No QueryClient set');
    return client;
  }

Context API의 3단계 패턴과 완전히 동일하다.

┌──────┬──────────────────┬────────────────┐
│ 단계 │ API │ 역할 │
├──────┼──────────────────┼────────────────┤
│ 1 │ createContext() │ 저장 공간 생성 │
├──────┼──────────────────┼────────────────┤
│ 2 │ Context.Provider │ 값 주입 │
├──────┼──────────────────┼────────────────┤
│ 3 │ useContext() │ 값 꺼내기 │
└──────┴──────────────────┴────────────────┘

즉, QueryClientProvider는 이 패턴을 라이브러리가 대신 구현해준 것이다. useContext의 역할은 새로운 클라이언트를 만드는게 아니라 Provider에 저장된 값의 참조를 반환하기 때문에 항상 같은 인스턴스를 반환한다.

번외로 아래와 같이 소비하는 컴포넌트의 예제 코드이다.

 import { useQueryClient, useMutation } from '@tanstack/react-query';

  function TodoItem({ id }: { id: number }) {                                                                            
    const queryClient = useQueryClient(); // QueryClient 인스턴스 꺼내기

    const deleteMutation = useMutation({
      mutationFn: () => deleteTodo(id),
      onSuccess: () => {
        // 삭제 후 목록 캐시 무효화 → 자동 리페치
        queryClient.invalidateQueries({ queryKey: ['todos'] });
      },
    });

    return (
      <button onClick={() => deleteMutation.mutate()}>
        삭제
      </button>
    );
  }

QueryClient 생성 후, 아래와 같이 래퍼 컴포넌트로 커스터마이징해서 사용할 수도 있다.

function QueryClientWithAlerts({ children }: { children: ReactNode }) {
  const alertApi = useApi(alertApiRef)
  const alertApiLatest = useRef(alertApi)
  alertApiLatest.current = alertApi

  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: {
          queries: {
            retry: 0,
            refetchOnWindowFocus: false,
            refetchOnReconnect: false,
          },
        },
        mutationCache: new MutationCache({
          onError: error => {
            alertApiLatest.current.post({
              message: error instanceof Error ? error.message : '요청 처리 중 오류가 발생했습니다.',
              severity: 'error',
            })
          },
        }),
      }),
  )

  return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}

순수한 QueryClientProvider를 그대로 노출하지 않고, 필요한 기능을 추가한 래퍼 컴포넌트로 감싸서 사용하는 패턴이다.

profile
안녕하세요

0개의 댓글