ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

React 数据请求:TanStack Query

React 数据请求:TanStack Query React 数据请求TanStack Query前置React 路由React Router目标用 Query 管理 loading / error / 缓存而不是手写一堆useEffect useState。引言组件里的useState管 UI 状态服务器上的列表 / 详情是另一类东西要请求、等 loading、处理失败、缓存、再刷新。手写useEffect fetch 三个 useState容易漏重试、漏竞态。TanStack Querytanstack/react-query专门管这类「服务端状态」。本篇 demo本地 mock 待办 600ms 延迟演示 loading / 列表 / 手动刷新离线也能跑。动手完整代码react2/07-data-fetching/democdreact2/07-data-fetching/demonpminstallnpmrun 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. 先挂QueryClientProviderimport { 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(): PromiseTodo[] { 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 classNameerr失败{(error as Error).message}/p return ( section button typebutton 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还没有成功数据首屏 loadingisError/error失败data成功后的结果过了 pending/error 分支后可放心用refetch手动再请求一次isFetching正在请求中含后台刷新已有数据时也可能为 trueisPendingvsisFetching首屏用isPending挡整页点刷新时已有列表用isFetching只禁用按钮 / 显示「刷新中」别把整页清空。4.queryKey怎么设计queryKey: [todos] // 整份列表 queryKey: [todo, id] // 某一条id 变就另缓存 queryKey: [todos, { q }] // 带筛选条件规则直觉key 变了 → 当作另一份查询同一 key → 尽量复用缓存少打重复请求5. 接到真实 API把queryFn换成fetch即可其它 UI 分支不用动async function fetchTodos(): PromiseTodo[] { const res await fetch(/api/todos) if (!res.ok) throw new Error(HTTP ${res.status}) return res.json() }建议在queryFn里对非 2xx抛错这样会走isError而不是把错误 HTML 当成data。对照表概念干什么demoQueryClient缓存与调度中心模块顶层new QueryClient()QueryClientProvider注入 client包住TodoListqueryKey缓存键[todos]queryFn拉数fetchTodosmock delayisPending/isError首屏状态加载中 / 失败文案refetchisFetching手动刷新「重新请求」按钮常见坑做法问题忘了 ProvideruseQuery直接报错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 大型项目目录骨架
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进