首页 / TanStack 生态入门教程 / Query 入门:安装与 QueryClient

TanStack 生态入门教程

Query 入门:安装与 QueryClient

本教程共 38 篇 · 第 4 篇 · 更新于 2026-07-27 · 约 8 分钟阅读

TanStackTanStack 生态入门教程TanStack QueryQueryClient安装useQueryProviderReact

4. Query 入门:安装与 QueryClient

本节目标:把 TanStack Query 装到项目里,跑通第一个查询,搞懂 QueryClient、Provider、useQuery 三者的关系,学完你能让数据请求在项目里跑起来。

4.1 安装

Query 的包名是 @tanstack/react-query,用你习惯的包管理器装:

# npm
npm i @tanstack/react-query

# pnpm
pnpm add @tanstack/react-query

# yarn
yarn add @tanstack/react-query

# bun
bun add @tanstack/react-query

环境要求:

  • React 18.0 或更高(v5 用了 useSyncExternalStore,老版本 React 不行)
  • 浏览器 Chrome 91+ / Firefox 90+ / Edge 91+ / Safari 15+
Note

Query 同时兼容 ReactDOM 和 React Native,用法一样。React Native 里监听焦点和网络状态要额外配置,后面遇到再说。

4.2 顺便装个 ESLint 插件

官方有个 ESLint 插件,能帮你抓依赖缺失、API 误用这类问题。建议开发时就装上:

npm i -D @tanstack/eslint-plugin-query

然后在 ESLint 配置里启用,具体规则可以看官方文档。这个插件能避免一些很隐蔽的坑,比如查询键里漏了依赖变量。

4.3 三大核心概念

写代码之前,先认识 Query 的三个核心概念,整个库都是围着它们转的:

  1. 查询(Query):用 useQuery 发起一个异步数据请求,带个唯一 key 标识
  2. 变更(Mutation):用 useMutation 修改服务端数据(增删改)
  3. 失效(Invalidation):用 queryClient.invalidateQueries 标记缓存过期,触发重新请求

这三个概念构成了 Query 的骨架。后面十几章都在讲怎么用它们。

4.4 创建 QueryClient

QueryClient 是整个 Query 的「大脑」,所有缓存、查询实例都归它管。第一步是创建它:

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

const queryClient = new QueryClient()

就这么简单。不传参数就是全用默认配置。但实际项目里你通常想调几个默认值:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // 数据多久算「过期」,默认 0(立即过期)
      staleTime: 1000 * 60,  // 1 分钟
      // 多久没人用就回收,默认 5 分钟
      gcTime: 1000 * 60 * 10,  // 10 分钟
      // 失败重试次数,默认 3
      retry: 3,
      // 窗口聚焦时是否重新请求,默认 true
      refetchOnWindowFocus: false,
    },
  },
})

这几个参数后面会专门讲,这里先知道能在 defaultOptions.queries 里统一配就行。

Tip

defaultOptions 里配的是全局默认。单个 useQuery 还能覆盖这些值,局部优先级高于全局。

4.5 挂载 Provider

QueryClient 创建好了,得让整个应用都能用到它。靠的是 QueryClientProvider

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

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

QueryClientProviderqueryClient 通过 React Context 往下传,应用里任何地方都能用 useQueryClient() 拿到它。

挂载位置一般在应用最外层,确保所有组件都在 Provider 内部。和 React Router、Redux Provider 这些放一起就行。

4.6 第一个 useQuery

Provider 挂好后,就能在组件里用 useQuery 发请求了。看个完整例子:

import { useQuery } from '@tanstack/react-query'

function Example() {
  const { isPending, error, data } = useQuery({
    queryKey: ['repoData'],
    queryFn: () =>
      fetch('https://api.github.com/repos/TanStack/query').then((res) =>
        res.json(),
      ),
  })

  if (isPending) return 'Loading...'

  if (error) return 'An error has occurred: ' + error.message

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>Stars: {data.stargazers_count}</strong>
    </div>
  )
}

拆解一下:

  1. queryKey: ['repoData'] — 这个查询的唯一标识,是个数组
  2. queryFn — 实际发请求的函数,必须返回 Promise
  3. isPending — 还没拿到数据时为 true
  4. error — 出错时的错误对象
  5. data — 成功时的数据

v5 的 useQuery 只接受对象签名。如果你看到老教程写 useQuery(['key'], fn),那是 v3/v4 的写法,v5 已经移除了。

Warning

老项目从 v4 升 v5,有个 codemod 能帮你自动改签名。但改完一定要 review,codemod 有边界情况处理不了。

4.7 用 useQueryClient 拿客户端

除了 useQuery,还有个常用 hook 是 useQueryClient,用来在组件里拿到那个 queryClient 实例:

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

function Todos() {
  const queryClient = useQueryClient()

  // 手动触发刷新、失效、读缓存等操作
  const refetch = () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  }

  return <button onClick={refetch}>刷新</button>
}

后面讲失效、乐观更新、缓存操作时,这个 hook 会频繁用到。

4.8 一个完整的小例子

把前面的东西串起来,写个能跑的 todo 列表:

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

function Todos() {
  const queryClient = useQueryClient()

  // 查询:拿 todo 列表
  const query = useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const res = await fetch('/api/todos')
      return res.json()
    },
  })

  // 变更:新增 todo
  const mutation = useMutation({
    mutationFn: async (newTodo) => {
      const res = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
      })
      return res.json()
    },
    onSuccess: () => {
      // 新增成功后,让 todo 列表失效重新请求
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  if (query.isPending) return <div>加载中...</div>
  if (query.isError) return <div>出错了:{query.error.message}</div>

  return (
    <div>
      <ul>
        {query.data.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <button
        onClick={() => {
          mutation.mutate({ id: Date.now(), title: '洗衣服' })
        }}
      >
        新增 Todo
      </button>
    </div>
  )
}

export default App

这个例子涵盖了三大核心概念:useQuery 查询、useMutation 变更、invalidateQueries 失效。Mutation 的具体用法后面会细讲,这里先看个全貌。

4.9 几个新手坑

坑一:忘记挂 Provider。 useQuery 在 Provider 外面用会报错。报错信息还算清楚,但第一次遇到会懵。

坑二:QueryClient 放在组件内部。 这样每次组件重渲染都会 new 一个新的 client,缓存全丢。正确做法是放在组件外层,或者用 useState 包一层:

// 推荐写法:放组件外
const queryClient = new QueryClient()

function App() {
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}

// SSR 场景:每个请求一个独立 client,避免数据串
function App() {
  const [queryClient] = useState(() => new QueryClient())
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}

坑三:fetch 不抛错。 原生 fetch 在 HTTP 状态码非 2xx 时不会自动 throw。你得手动判断 response.ok,不然 Query 会以为请求成功,把错误响应当数据存进缓存:

queryFn: async () => {
  const response = await fetch('/api/todos')
  if (!response.ok) {
    throw new Error('请求失败:' + response.status)
  }
  return response.json()
}
Tip

如果嫌每个 queryFn 都写 response.ok 判断麻烦,可以封装个统一的 fetcher 函数,或者直接用 axios(它会自动抛错)。

4.10 小结

Query 的上手就三步:装包、建 QueryClient、挂 Provider。然后就能在任意组件里用 useQuery 发请求了。

记住几个要点:QueryClient 放组件外层别放里面;v5 只支持对象签名;fetch 要手动判断 response.ok

下一篇我们细讲 useQuery 的各种用法。