欢迎光临
我们一直在努力

React 数据请求:TanStack Query

React 数据请求:TanStack Query

前置:React 路由:React Router 目标:用 Query 管理 loading / error / 缓存,而不是手写一堆 useEffect + useState。

引言

组件里的 useState 管 UI 状态;服务器上的列表 / 详情是另一类东西:要请求、等 loading、处理失败、缓存、再刷新。

手写 useEffect + fetch + 三个 useState 容易漏重试、漏竞态。TanStack Query(@tanstack/react-query)专门管这类「服务端状态」。

本篇 demo:本地 mock 待办 + 600ms 延迟,演示 loading / 列表 / 手动刷新(离线也能跑)。

动手

完整代码:react2/07-data-fetching/demo

cd react2/07-data-fetching/demo
npm install && npm run dev

打开后先看到「加载中…」,再出列表;点「重新请求」会再走一遍 delay。


1. 为什么不用纯 useEffect

手写常见形态:

const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)

useEffect(() => {
fetch('/api/todos')
.then((r) => r.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [])

缺什么:取消过期请求、窗口切回自动刷新、同 key 去重、失败重试、缓存共享……Query 把这些默认带上。


2. 先挂 QueryClientProvider

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

const client = new QueryClient()

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

useQuery 必须在 Provider 里面用。一般在应用根(App 或 main)包一层即可。


3. useQuery:声明「要这份数据」

async function fetchTodos(): Promise<Todo[]> {
await new Promise((r) => setTimeout(r, 600)) // 假装网络
return MOCK_TODOS
}

function TodoList() {
const { data, isPending, isError, error, refetch, isFetching } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})

if (isPending) return <p>加载中…</p>
if (isError) return <p className="err">失败:{(error as Error).message}</p>

return (
<section>
<button type="button" onClick={() => refetch()} disabled={isFetching}>
{isFetching ? '刷新中…' : '重新请求'}
</button>
<ul>
{data.map((t) => (
<li key={t.id}>
<span className={t.completed ? 'done' : ''}>{t.title}</span>
</li>
))}
</ul>
</section>
)
}

字段含义
queryKey 这份数据的缓存身份证;相同 key 共享缓存
queryFn 真正去拉数据的异步函数
isPending 还没有成功数据(首屏 loading)
isError / error 失败
data 成功后的结果(过了 pending/error 分支后可放心用)
refetch 手动再请求一次
isFetching 正在请求中(含后台刷新;已有数据时也可能为 true)

isPending vs isFetching: 首屏用 isPending 挡整页;点刷新时已有列表,用 isFetching 只禁用按钮 / 显示「刷新中」,别把整页清空。


4. queryKey 怎么设计

queryKey: ['todos'] // 整份列表
queryKey: ['todo', id] // 某一条(id 变就另缓存)
queryKey: ['todos', { q }] // 带筛选条件

规则直觉:

  • key 变了 → 当作另一份查询
  • 同一 key → 尽量复用缓存,少打重复请求

5. 接到真实 API

把 queryFn 换成 fetch 即可,其它 UI 分支不用动:

async function fetchTodos(): Promise<Todo[]> {
const res = await fetch('/api/todos')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
}

建议:在 queryFn 里对非 2xx 抛错,这样会走 isError,而不是把错误 HTML 当成 data。


对照表

概念干什么demo
QueryClient 缓存与调度中心 模块顶层 new QueryClient()
QueryClientProvider 注入 client 包住 TodoList
queryKey 缓存键 ['todos']
queryFn 拉数 fetchTodos(mock delay)
isPending / isError 首屏状态 加载中 / 失败文案
refetch + isFetching 手动刷新 「重新请求」按钮

常见坑

做法问题
忘了 Provider useQuery 直接报错
queryKey 写成每次新对象却又不稳定 缓存命中乱、反复请求
只用 isFetching 做首屏 已有缓存时可能闪一下空态,语义和 isPending 不同
queryFn 失败却 return null 不 throw 不会进 isError
和 useState 里再存一份 data 双数据源,以 Query 的 data 为准

demo 在练什么

  • Provider + useQuery 拉 mock 列表
  • isPending / isError 分支
  • refetch 手动刷新,用 isFetching 反馈按钮状态
  • 自己改一改:让 fetchTodos 随机 throw new Error('网络炸了'),看错误分支;或把 key 改成 ['todos', query] 练筛选。


    运行截图

    远程待办列表

    下一篇

    React 大型项目目录骨架

    赞(0)
    未经允许不得转载:171主机测评 » React 数据请求:TanStack Query
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址