
UseQueryResult 类型详解读懂 tanstack/preact-query 中 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/preact-query中调用useQuery后拿到的解构对象data、status、isPending、refetch等其 TypeScript 类型并非散乱的接口而是由一个统一的类型别名UseQueryResultTData, TError严格刻画。本篇以仓库中 UseQueryResult 类型文档 为骨架结合preact-query与query-core两个包的源码讲清这个类型的定义位置、两个泛型参数的含义、它内部是如何由五类具体结果联合而成、每个字段的状态语义以及useQuery在何种情况下会返回它的“变体”DefinedUseQueryResult。读完你将能精准地为带select、initialData、依赖查询等场景标注类型也能通过判别字段正确编写 loading/error/success 分支逻辑。一句话定义useQuery的返回值类型UseQueryResult.md 中的完整定义只有一行类型别名type UseQueryResultTData, TError UseBaseQueryResultTData, TError也就是说UseQueryResult并没有自己的独立字段结构它是对UseBaseQueryResult的直接别名而继续沿着源码追溯packages/preact-query/src/types.ts 中UseBaseQueryResult又等价于 query-core 的QueryObserverResultexport type UseBaseQueryResult TData unknown, TError DefaultError, QueryObserverResultTData, TError export type UseQueryResult TData unknown, TError DefaultError, UseBaseQueryResultTData, TError因此在仓库中该类型被注释为 Re-exports QueryObserverResult fromtanstack/query-core即它本质是从 query-core 核心层再导出re-export的观察者结果类型。这种分层设计意味着无论你用的是 Preact 适配层、React 适配层还是 Vue/Solid 适配层最终落到组件里的结果对象形状都由query-core统一保证各框架包只负责在表层补充少量自己的字段例如 Preact 适配层额外支持关闭订阅的subscribed选项见 types.ts。两个类型参数TData 与 TError文档原样列出了UseQueryResult的两个泛型参数默认值与语义如下类型参数默认值语义TDataunknown传给select处理之后data字段最终呈现出的数据类型TErrorDefaultErrorqueryFn可能抛出的错误类型关于TData需要特别区分三个“数据”TQueryFnDataqueryFn的返回值类型例如接口直接返回的Post[]TDataselect之后的数据类型如果你配置了select: (posts) posts.length那么组件里data的类型会被推导为number缓存中真正存放的数据通常是TQueryFnData它是select与placeholderData的输入。这一点在 useBaseQueryOptions 的源码注释 中有明确表述TData默认取TQueryFnData仅在使用了select时二者才分离。所以当你在自定义 Hooks 里需要接受“任意查询的返回值”时可以放心使用同文件提供的AnyUseBaseQueryOptions/AnyUseQueryOptions把泛型参数置为any而返回侧对应的宽松类型则如 DefinedUseQueryResult 等兄弟类型文档 所示层层传递。TError默认是DefaultError——query-core提供的默认错误类型占位用于表示“未显式声明错误类型时queryFn抛出的错误按默认约定处理”。当你的queryFn明确抛出特定错误类型时例如自定义APIError应显式声明第二个泛型useQueryPost[], APIError, Post[]({ queryKey: [posts], queryFn: fetchPosts }) // result 类型即 UseQueryResultPost[], APIError内部结构由五个“状态专属”接口联合而成QueryObserverResult并不是单个扁平接口而是联合类型。查看 packages/query-core/src/types.tsexport type DefinedQueryObserverResultTData, TError | QueryObserverRefetchErrorResultTData, TError | QueryObserverSuccessResultTData, TError export type QueryObserverResultTData unknown, TError DefaultError | DefinedQueryObserverResultTData, TError | QueryObserverLoadingErrorResultTData, TError | QueryObserverLoadingResultTData, TError | QueryObserverPendingResultTData, TError | QueryObserverPlaceholderResultTData, TError联合的五个成员接口把查询生命周期切成了五类并且每个成员都通过可判别字段把状态写死这正是 TypeScript 能帮你在分支中自动收窄类型的关键成员接口status关键判别字段数据/错误表现QueryObserverPendingResultpendingisPending: true、isLoading: falsedata: undefined、error: null尚无缓存且未完成任何请求QueryObserverLoadingResultpendingisPending: true、isLoading: truedata: undefined、error: null首次请求进行中QueryObserverLoadingErrorResulterrorisLoadingError: true、isRefetchError: falsedata: undefined、error: TError首次加载失败QueryObserverRefetchErrorResulterrorisRefetchError: truedata: TData、error: TError后台重取失败但已有旧数据QueryObserverSuccessResultsuccessisSuccess: truedata: TData、error: nullQueryObserverPlaceholderResultsuccessisPlaceholderData: truedata: TData展示的是 placeholder 数据留意一个细节error状态被拆成isLoadingError与isRefetchError两个子接口——前者发生在没有数据可展示的首次失败后者发生在已有数据时后台刷新失败。二者最直观的差别体现在data上QueryObserverRefetchErrorResult中data依然是TData有旧数据兜底所以解构出来绝不会是undefined。完整字段速查继承自 QueryObserverBaseResult所有成员接口都继承自QueryObserverBaseResultTData, TError见 query-core/src/types.ts因此无论处于哪种状态useQuery的返回值都稳定包含以下字段字段含义data最近一次成功解析的数据select之后无缓存时为undefineddataUpdatedAt最近一次进入success状态的时间戳error查询抛出的错误对象默认nullerrorUpdatedAt最近一次进入error状态的时间戳errorUpdateCount累计错误次数failureCount失败计数每次失败 1成功后归 0failureReason触发重试的失败原因成功后重置为nullstatuspending \| error \| success派生布尔量的来源fetchStatusfetching \| paused \| idle描述网络请求本身的进行状态isPending无缓存数据且无完成的请求时为trueisLoading等价于isFetching isPending首次请求进行中为trueisFetchingqueryFn正在执行时为true含首次与后台重取isRefetching等价于isFetching !isPending仅后台重取为trueisError/isSuccess分别对应status error/status success的便捷布尔量isLoadingError/isRefetchError首次加载失败 / 重取失败isStale数据被 invalidate 或已超过staleTime时为trueisPlaceholderData当前展示的是placeholderData时为trueisFetched/isFetchedAfterMount查询是否已获取过数据 / 是否在组件挂载后被获取isPaused查询想请求但因网络模式被暂停isEnabled当前 observer 是否处于启用状态与enabled选项对应isInitialLoading已废弃deprecated请改用isLoadingrefetch手动重新请求的函数返回PromiseQueryObserverResultTData, TError这里最容易被误解的是status与fetchStatus的双轴设计status描述“有没有数据可展示”而fetchStatus描述“网络请求有没有在进行”。例如一个已有缓存、正在后台刷新的查询它的status是success、fetchStatus是fetching——这也是为什么 UI 上同时展示“内容 后台更新提示”是可行的。useQuery 在什么情况下返回本类型UseQueryResult是useQuery的“普通形态”返回值但要注意useQuery声明了多个重载。查看 packages/preact-query/src/useQuery.ts两种重载按initialData是否存在二选一设置了initialData的重载返回DefinedUseQueryResultTData, TError由于初始数据保证查询一上来就有内容联合类型被收窄为“错误重取/成功”两种成员data永不可能是undefined对应源码status永不为pending未设置initialData即UndefinedInitialDataOptions的重载返回UseQueryResultTData, TError查询处于pending时data可能是undefined。因此文档注释里那一句 “Same as UseBaseQueryResult” 的潜台词是UseQueryResult恰恰代表“允许data为undefined”的场景。典型用法是 v5 推荐的“先判分支再使用数据”写法import { useQuery } from tanstack/preact-query function Posts() { const { status, data, error, isFetching } useQuery({ queryKey: [posts], queryFn: fetchPosts, }) if (status pending) return Loading... if (status error) return spanError: {error.message}/span return ( div ul {data.map((post) li key{post.id}{post.title}/li)} /ul div{isFetching ? Background Updating... : }/div /div ) }由于QueryObserverResult是带可判别字段的联合类型在上述if分支之后 TypeScript 会自动把data收窄为Post[]无需非空断言。同样的判断也可以改用派生布尔量isPending/isError来表达两种风格在 useQuery.ts 的文档示例 中均有对应代码。结合 select 与依赖查询的使用因为TData表示“select之后的数据”UseQueryResult的类型会随select的返回类型联动import { useQuery } from tanstack/preact-query function PostCount() { const { data, isPending, isError, error } useQuery({ queryKey: [posts], queryFn: fetchPosts, select: (posts) posts.length, // data 的类型被推导为 number }) if (isPending) return Loading... if (isError) return spanError: {error.message}/span return span{data} posts/span // data: number }对于依赖查询仅当条件满足才启用useQuery.ts 的示例 提醒应使用isLoading而非isPending判断加载态避免查询被禁用enabled: false时误渲染 loading此时因为data理论上可能为undefined直接访问data.title需要用可选链import { useQuery } from tanstack/preact-query function Post({ postId }: { postId: number | undefined }) { const { data, isLoading, isError, error } useQuery({ queryKey: [post, postId], queryFn: () fetchPost(postId!), enabled: postId ! null, }) if (postId null) return Select a post if (isLoading) return Loading... if (isError) return spanError: {error.message}/span return h1{data?.title}/h1 }如何在组件类型标注中使用它当你编写“渲染查询结果”的公共子组件、或给自定义 Hook 补充显式返回值类型时直接从包中导入该类型别名即可例如import type { UseQueryResult } from tanstack/preact-query function renderPosts(result: UseQueryResultPost[]) { const { data, isPending, isError, error } result if (isPending) return Loading... if (isError) return spanError: {error.message}/span return data.map((post) li key{post.id}{post.title}/li) }如果业务上能保证结果必有数据例如查询设置了initialData或你使用useSuspenseQuery则应改注DefinedUseQueryResultPost[]而不是本类型——否则会因为data可空而被迫写多余的空值分支。这两类“结果”的关系在 DefinedUseQueryResult.md 与 UseBaseQueryResult.md 中有对称说明。与相邻结果类型的对照preact-query中围绕useQuery家族的结果类型还有几个“近亲”它们都定义在 packages/preact-query/src/types.ts类型别名来源关键差异UseQueryResult本文主角等价UseBaseQueryResultpending时data可为undefinedDefinedUseQueryResultDefinedQueryObserverResult设置initialData时useQuery的返回值data永不undefinedUseSuspenseQueryResultDefinedQueryObserverResult剔除isPlaceholderDatauseSuspenseQuery的返回值Suspense 永不渲染占位数据故该布尔字段恒为false被直接删除而非保留UseInfiniteQueryResultInfiniteQueryObserverResultuseInfiniteQuery的返回值附加fetchNextPage、hasNextPage等分页字段注意一个设计取向Preact 适配层用调用哪个 Hook 来区分是否走 Suspense而不是暴露suspense选项types.ts 的注释明确指出UseQueryOptions去掉了suspense。这解释了为什么UseSuspenseQueryResult与UseQueryResult是两个并列类型而非同一个类型加布尔开关。底层原理useBaseQuery 如何产出这个结果最终让useQuery返回UseQueryResult的是 packages/preact-query/src/useBaseQuery.ts 中的useBaseQuery实现入口处显式标注返回类型即为 query-core 的QueryObserverResultTData, TError。整体调用链为useQuery(options)把调用透传给useBaseQuery(options, QueryObserver, queryClient)见 useQuery.tsuseBaseQuery先通过useQueryClient(queryClient)拿到QueryClient并用client.defaultQueryOptions(options)填充默认值从getQueryCache()中按queryHash定位已有查询随后在useState惰性初始化中创建QueryObserver实例立即调用observer.getOptimisticResult(defaultedOptions)得到“乐观结果”useBaseQuery.ts——即便尚未真正订阅也先按status: pending / fetching的乐观状态返回这正是 v5 中“结果先乐观置为 fetching 再订阅更新”的来源通过useSyncExternalStore订阅 observernotifyManager.batchCalls批量派发变更并在订阅回调中补充一次observer.updateResult()防止错过创建与订阅之间的更新返回前若未显式配置notifyOnChangeProps还会执行observer.trackResult(result)做结果属性使用追踪useBaseQuery.ts记录组件渲染时实际读取了哪些字段从而把不必要的数据变更导致的 re-render 压缩到最小Suspense 场景shouldSuspend与错误边界场景getHasError则通过直接throw交由 Preact 上层处理正常路径才把QueryObserverResult交给组件。从源码结构还可以推断UseQueryResult联合成员的每次切换都由observer的状态机驱动查询被 invalidate、staleTime过期触发重取、placeholderData/keepPreviousData展示占位数据等事件最终都会反映为返回对象中status、fetchStatus与各派生布尔量的相应组合而类型层早已为每一种组合准备好了对应的成员接口——这就是“类型与运行状态一一对应”的保证。小结UseQueryResultTData, TError是 preact-query 类型文档 中定义的useQuery返回值别名直接等价于UseBaseQueryResult再导出自tanstack/query-core的QueryObserverResult。TData描述select之后data的类型TError描述queryFn可能抛出的错误类型二者默认值分别为unknown与DefaultError。它是由 56 个带判别字段的状态接口组成的联合类型用status、isLoading、isError、isPlaceholderData等字段即可让 TypeScript 完成精确收窄。若useQuery设置了initialData返回类型升级为DefinedUseQueryResultdata永不空而useSuspenseQuery/useInfiniteQuery又各自衍生出UseSuspenseQueryResult/UseInfiniteQueryResult使用时注意区分。【免费下载链接】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),仅供参考