资讯动态

TanStack Query 依赖查询(Dependent Queries)完全指南:useQuery 串行数据获取、状态机与性能优化

发布时间:2026/9/10 14:00:05 来源:尧图企业网站定制
TanStack Query 依赖查询Dependent Queries完全指南useQuery 串行数据获取、状态机与性能优化【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query本指南围绕 TanStack Query本仓库packages/query-core/packages/react-query对应源码中的Dependent Queries依赖查询又称串行查询展开讲解如何用enabled选项让第二个查询在前一个查询完成之后才发起请求覆盖useQuery与useQueries两种形态、status/fetchStatus状态迁移规律以及其背后隐藏的“请求瀑布”性能代价与优化方案。读完本文你将能够正确实现有依赖关系的数据获取并理解何时该重构 API、何时该借助 prefetch 与 Server Components 消除串行请求。说明本指南基于 React 框架适配层编写示例使用tanstack/react-query但依赖查询的底层机制全部位于框架无关的query-core中同样适用于本仓库下 Angular、Solid、Svelte、Vue 等适配层。什么是依赖查询依赖查询Dependent Queries指的是前一个查询必须先完成、后一个查询才能开始执行的查询组合。例如“先用邮箱拿到用户信息再用拿到的userId去拉取该项目列表”第二步请求的参数本身依赖第一步的返回结果因此二者天然存在先后关系无法并行。在 TanStack Query 中实现这种依赖关系“简单到只需要一个选项”——enabled。该选项用于告诉一个查询“你何时才允许执行”// 第一步根据邮箱获取用户 const { data: user } useQuery({ queryKey: [user, email], queryFn: getUserByEmail, }) const userId user?.id // 第二步拿到 userId 之后再获取该用户的项目列表 const { status, fetchStatus, data: projects, } useQuery({ queryKey: [projects, userId], queryFn: getProjectsByUser, // 在 userId 存在之前本查询不会执行 enabled: !!userId, })当enabled: false时查询不会自动发起请求一旦enabled变为true查询便会立即按照既有的挂载/重取策略执行。这也是实现“点击按钮后再请求”“表单校验通过后再请求”等场景的通用手段。enabled 的取值类型与语义从 packages/query-core/src/types.ts 的类型定义可以看到/** * Set this to false or a function that returns false to disable automatic * refetching when the query mounts or changes query keys. * ... * Accepts a boolean or function that returns a boolean. * Defaults to true. */ enabled?: QueryBooleanOptionTQueryFnData, TError, TQueryData, TQueryKey也就是说enabled默认值为true不写即自动执行它接受布尔值也接受**“以 query 为参数、返回布尔值的函数”**例如enabled: (query) query.state.data ! undefined将enabled设为false时查询在挂载或 query key 变化时都不会自动 refetch只能通过返回的refetch方法手动触发query-core对非法取值做了运行时校验。在 packages/query-core/src/queryObserver.ts 中enabled既非布尔、也非函数、且解析结果不是布尔时会抛出Expected enabled to be a boolean or a callback that returns a boolean错误。对“永久禁用”的场景仓库还提供了替代写法直接在queryFn位置传入skipToken让 TypeScript 推断被禁用的查询相关用法可见 queryOptions.test-d.tsx 的用例不过依赖查询通常需要的是“先禁用、数据到达后自动启用”因此用enabled: !!userId这种动态开关更贴合需求。依赖查询的状态迁移status 与 fetchStatus 的配合理解依赖查询的关键在于分清 TanStack Query 的两个状态维度status查询状态pending/success/error描述是否有数据fetchStatus获取状态fetching/idle/paused描述此刻是否正在发起网络请求。对于上面的projects查询在userId尚未就绪时它处于status: pending isPending: true fetchStatus: idle注意fetchStatus: idle——此时查询并未被禁止status仍是pending只是由于enabled: false而“待命”不会发起任何请求。这正是enabled与直接条件渲染的差异查询对象始终存在且被观察着只是暂停触发网络动作。一旦user数据到达、enabled变为trueprojects查询随即被激活并转入status: pending isPending: true fetchStatus: fetching这里status仍为pending因为数据还没回来但fetchStatus切换为fetching表示请求正在进行中。当 projects 数据返回后最终收敛到status: success isPending: false fetchStatus: idle上述状态迁移可以直接在 packages/query-core/src/queryObserver.ts 的结果组装逻辑中找到印证isLoading isPending isFetching、isInitialLoading等价于isLoading、而isEnabled由resolveQueryValue(options.enabled, query) ! false计算得出。从源码看 enabled 如何“掐断”请求query-core中enabled的开关作用贯穿“是否加载、是否挂载后取数、窗口聚焦/断线重连时是否 refetch、是否过期”等全部分支。以 packages/query-core/src/queryObserver.ts 中的几个决策函数为例shouldLoadOnMount要求enabled ! false且data undefined才允许首次加载shouldFetchOn供refetchOnMount、refetchOnWindowFocus、refetchOnReconnect共用只有enabled ! false且数据过期stale时才会触发重取shouldFetchOptionally当“查询对象发生切换或上一轮enabled false”时依赖失效状态判断是否应立即取数——这正是enabled从false翻转为true后立即触发请求的机制所在isStaleenabled false的查询不会被判定为过期因此禁用状态下的查询不会因窗口聚焦、断线重连等行为而被拉取。同样地查询条目层在 packages/query-core/src/query.ts 定义了isActive()存在任一enabled ! false的观察者与isDisabled()。当一个依赖查询因前序数据未到而停摆时它是“存在但未被激活”的。useQueries 依赖查询动态并行查询useQueries的定位是以并行方式同时管理多个查询因此它最常见的形态是“先有一个获取 id 列表的查询然后把这个列表 map 成一批并行的子查询”。这种由“一次返回结果决定后续 N 个请求”的结构也是一种依赖查询——第二步批量查询依赖第一步的数据// 第一步获取所有用户的 id const { data: userIds } useQuery({ queryKey: [users], queryFn: getUsersData, select: (users) users.map((user) user.id), }) // 第二步为每个 userId 发起一个查询获取各自的消息 const usersMessages useQueries({ queries: userIds ? userIds.map((id) { return { queryKey: [messages, id], queryFn: () getMessagesByUsers(id), } }) : [], // 若 userIds 尚未就绪undefined返回空数组 })这里的关键技巧在于三元表达式兜底当userIds还是undefined时queries传入空数组useQueries不观察任何查询一旦userIds数据到达map 出的查询数组便会在同一次渲染中全部激活且这批messages查询之间彼此并行。需要特别注意的是useQueries返回的是一个“查询结果数组”每一项对应传入数组中的一个查询其类型签名可在 packages/react-query/src/useQueries.ts 中看到未提供combine时返回与queries顺序一致的UseQueryResult[]。从实现上看useQueries内部把整个查询数组统一交给QueriesObserver管理见 packages/query-core/src/queriesObserver.ts并在渲染时通过client.defaultQueryOptions对每个查询选项做默认化与“乐观 fetch 状态”标记见 useQueries.ts随后使用useSyncExternalStore订阅批量更新。这保证了数据依赖就绪前后同一数组内的查询能以一致、高效的方式被增删。用 combine 聚合依赖结果当后续逻辑只关心“是否全部完成 / 是否出错的汇总状态”时可以借助combine把结果数组合并为单一返回值。仓库 useQueries.ts 给出了参考实现const { data, isPending, isError } useQueries({ queries: userIds ? userIds.map((id) ({ ... })) : [], combine: (userQueries) ({ data: userQueries.map((query) query.data), isPending: userQueries.some((query) query.isPending), isError: userQueries.some((query) query.isError), }), })注意combine的结果在QueriesObserver中会经过结构共享replaceEqualDeep处理以保证引用稳定性见 queriesObserver.ts。combine是一个内联函数时每次渲染都会重新执行若计算代价大可考虑提取为稳定引用。使用 useQueries 时的错误处理提醒当第一步查询失败时userIds将保持undefinedqueries会一直拿到空数组第二步因此永远不会发起。这是数据驱动型依赖查询的固有行为需要在业务上显式处理“第一步失败”的 UI 反馈例如单独渲染第一步的isError分支避免页面停留在空转的加载状态。真实示例从仓库代码看依赖查询的落地依赖查询并不是纸上谈兵。仓库示例 examples/react/basic/src/index.tsx 就是一个典型的“未选中帖子时禁用查询、选中后再请求”的依赖式用法function usePost(postId: number) { return useQuery({ queryKey: [post, postId], queryFn: () getPostById(postId), enabled: !!postId, }) }在这个帖子浏览示例里postId初始为无值状态此时usePost的查询enabled: false、不会打网络请求一旦用户点击某篇帖子使postId生效查询才被激活并发起getPostById。这说明enabled依赖的值不一定是上一个查询的返回数据也可以是组件的 props、路由参数、表单状态等任何“稍后才变得可用”的值——凡是“等某个条件成立才请求”的查询本质都属于依赖查询的范畴。其它框架适配层也有同主题示例可交叉参考例如 examples/vue/dependent-queries 目录下的 Vue 版本依赖查询指南本身在多框架文档下均有对应页如 Angular 依赖查询指南、Lit 依赖查询指南。性能提醒依赖查询就是请求瀑布依赖查询在定义上就构成了一种请求瀑布request waterfall第二个请求必须等第一个请求的网络往返结束才能开始。关于请求瀑布的系统性讨论见 Performance Request Waterfalls 指南。其危害在数学上很直观假设两个查询耗时相同串行执行一个完成后再开始另一个永远比并行执行多花一倍时间如果发生在高延迟网络环境下这种额外开销会被进一步放大。因此能用并行就不要串行。优先重构后端 API让两个数据源可以在一次往返中取到。以本文开头示例为例与其“先getUserByEmail拿到userId再getProjectsByUser”不如后端新增一个getProjectsByUserEmail查询让两个数据在一次请求里同时返回从而抹平flatten瀑布。当“重构 API 可行但成本高”或“重构并不现实”时官方文档推荐的其它缓解路径还有Prefetching 路由集成在页面加载或路由切换时提前并行预取见 Prefetching Router Integration 指南把瀑布提前到用户可见之前把瀑布搬到服务端利用 Server Components 在延迟更低的服务器上完成串行数据获取见 Advanced Server Rendering 指南——但这通常是大规模架构调整需要权衡收益服务端渲染预取在 SSR 阶段并行预取依赖链上的查询再通过 hydration 交给客户端见 SSR Hydration 指南。值得留意的是请求瀑布不止发生在“同一个组件里先后两次useQuery”。父组件等数据完成后才渲染内部含查询的子组件Nested Component Waterfalls、以及 Suspense 模式下组件内多个useSuspenseQuery被串行挂起可用useSuspenseQueries合并为并行同样会制造瀑布具体模式可参考 request-waterfalls.md 中的分类与拆解示例。日常开发中可以用浏览器 Network 面板观察请求的串行链条重点排查高影响节点。小结依赖查询 串行查询后一个查询的执行依赖前一个查询的结果用enabled布尔开关或返回布尔的函数即可精确控制执行时机掌握状态机依赖未满足时查询处于status: pendingfetchStatus: idle条件满足后进入fetchStatus: fetching取数完成落到status: success。区分status有无数据与fetchStatus是否在请求是理解整个库行为的基础useQueries做批量依赖先取 id 列表再 map 成并行子查询数组注意用三元表达式兜底undefined并用combine聚合整体结果把性能放第一位依赖查询天然是请求瀑布优先通过后端合并接口、路由级 prefetch 或 Server Components 手段把串行请求转成并行避免高延迟场景下的双倍等待。无论是“拿 userId 查 projects”的经典两步请求还是“先取 id 列表、再批量拉取详情”的动态并行enabled与依赖查询机制都是 TanStack Query 组织复杂数据流的核心工具在 docs/framework/react/guides/dependent-queries.md 原始指南的基础上结合本仓库 query-core 源码 与 示例代码 一起阅读即可对它的行为边界与性能影响形成完整认识。【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价