ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Preact Query 中 `UseInfiniteQueryResult` 返回值类型详解:useInfiniteQuery 的结果类型与空数据状态分析

Preact Query 中 `UseInfiniteQueryResult` 返回值类型详解:useInfiniteQuery 的结果类型与空数据状态分析 Preact Query 中UseInfiniteQueryResult返回值类型详解useInfiniteQuery 的结果类型与空数据状态分析【免费下载链接】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/queryUseInfiniteQueryResult是 TanStack Query 在 Preact 框架适配层tanstack/preact-query中为useInfiniteQueryHook 提供的基础返回值类型。它本身是一个类型别名直接重导出re-export自tanstack/query-core的InfiniteQueryObserverResult用于描述「未设置initialData」时无限查询从 pending、error 到 success 全生命周期的结果形态。读完本文你将掌握该类型的精确定义位置与结构、data在何种场景下为undefined、两个类型参数的含义与推导方式以及它与DefinedUseInfiniteQueryResult、UseQueryResult等相关类型的区别并能在 Preact 应用中安全地消费无限分页数据。一、类型定义一行别名的背后是整套无限查询结果协议在 packages/preact-query/src/types.ts 中该类型别名被定义在第 364367 行/** * The result of useInfiniteQuery when initialData isnt set — data may be undefined while the query is * pending. Re-exports {link InfiniteQueryObserverResult} from tanstack/query-core. */ export type UseInfiniteQueryResult TData unknown, TError DefaultError, InfiniteQueryObserverResultTData, TError可以看到Preact 适配层并没有为无限查询重新设计一套结果类型而是把「骨架」InfiniteQueryObserverResult留在与框架无关的核心包tanstack/query-core中再由每个框架包以自己的名字做一次薄薄的转写React 版本对应UseInfiniteQueryResult见packages/react-query中的同名别名Preact 版本即本文讨论的 UseInfiniteQueryResult.md核心层定义见 packages/query-core/src/types.ts 第 10581066 行的InfiniteQueryObserverResult。这种「核心定义 框架转写」的架构是 TanStack Query 多框架同构设计的典型体现useInfiniteQuery、useQuery等 Hook 在不同框架下共享同一套 observer 与查询状态机类型系统也随之保持框架间行为一致。别名 vs 接口为什么用type该结果既可能是pending状态data为undefined又可能是success状态data必然有值还可能是带错误的重取refetch error状态。不同类型下同一字段的可空性完全不同只有「可辨识联合类型」才能精确表达这种随status收缩的形态。因此实现采用type联合而非interface继承这是保证类型收窄type narrowing能够生效的前提——interface无法做同样严格的字段冲突检查。二、联合类型结构五个状态分支如何刻画无限查询生命周期InfiniteQueryObserverResult在 packages/query-core/src/types.ts 第 10581066 行的完整定义为export type InfiniteQueryObserverResult TData unknown, TError DefaultError, | DefinedInfiniteQueryObserverResultTData, TError | InfiniteQueryObserverLoadingErrorResultTData, TError | InfiniteQueryObserverLoadingResultTData, TError | InfiniteQueryObserverPendingResultTData, TError | InfiniteQueryObserverPlaceholderResultTData, TError其中的五个分支同文件各段定义共同决定了data、error与各状态位status、isPending、isSuccess等的合法组合状态分支位置dataerrorstatusisPending/isSuccessInfiniteQueryObserverPendingResulttypes.ts#L946-L979undefinednullpendingisPending: true、isSuccess: falseInfiniteQueryObserverLoadingResulttypes.ts#L963-L979undefinednullpending同时满足isLoading: trueInfiniteQueryObserverLoadingErrorResulttypes.ts#L981-L997undefinedTErrorerrorisLoadingError: trueInfiniteQueryObserverSuccessResulttypes.ts#L1015-L1031TDatanullsuccessisSuccess: trueInfiniteQueryObserverRefetchErrorResulttypes.ts#L999-L1013TDataTErrorerrorisRefetchError: trueInfiniteQueryObserverPlaceholderResulttypes.ts#L1033-L1049TDatanullsuccessisPlaceholderData: trueDefinedInfiniteQueryObserverResult则只由RefetchError与Success两个「已有数据」的分支组成types.ts#L1051-L1056。为何「没有 initialData 时 data 可能是 undefined」这正是本类型最核心的语义官方文档也据此给出说明当useInfiniteQuery未配置initialData时首次加载期间查询处于pending状态此时对应InfiniteQueryObserverLoadingResult/InfiniteQueryObserverPendingResult分支其data被明确收窄为undefined。由此得到一个重要的编程结论在拿到结果后必须先对status或isPending/isError等布尔位做判断TypeScript 才会把data收窄为有值类型。如果直接编写data.pages.map(...)编译器会报错——这是类型系统刻意留给你的「安全护栏」提醒你处理加载态而不是运行时才炸出undefined访问错误。三、类型参数TData 与 TError该别名接受两个泛型参数默认值均与 query-core 保持一致TData默认unknown「select执行之后data的最终类型」。无限查询的选项支持select变换因此TData指的是变换产物在结果对象data.pages数组元素上的类型而不是原始queryFn的原始返回类型。TError默认DefaultError「queryFn可能抛出的错误类型」。DefaultError在 query-core 中默认取Error当你的queryFn抛出自定义错误例如 API 客户端错误类时通过useInfiniteQuery的泛型参数把它传入error字段与isError分支即可得到精确的类型收窄。两者可同时通过 Hook 调用推导。useInfiniteQuery的函数签名useInfiniteQuery.ts#L344-L359实际带有 5 个泛型TQueryFnDataqueryFn 原始数据、TError、TData经 select 后、TQueryKey、TPageParam。若TData未显式给定默认推导为InfiniteDataTQueryFnData, unknown——即一个{ pages: TQueryFnData[]; pageParams: unknown[] }形态的数据容器InfiniteData定义见 packages/query-core/src/types.ts#L210-L213。四、姊妹类型DefinedUseInfiniteQueryResult 与 useInfiniteQuery 的重载切换UseInfiniteQueryResult只在「未设置initialData」时被使用当设置了initialData返回值类型会切换为DefinedUseInfiniteQueryResult——在该类型下data永远不为undefined即使在重取失败时也有旧数据兜底。两者在 packages/preact-query/src/types.ts 中的定义紧邻且对称// L364-L367无 initialData → 数据可能为空 export type UseInfiniteQueryResultTData unknown, TError DefaultError InfiniteQueryObserverResultTData, TError // L376-L379有 initialData → 数据必定有值 export type DefinedUseInfiniteQueryResultTData unknown, TError DefaultError DefinedInfiniteQueryObserverResultTData, TErroruseInfiniteQuery正是靠函数重载在这两者间自动切换实现见 packages/preact-query/src/useInfiniteQuery.ts第 64 行的重载接收DefinedInitialDataInfiniteOptions返回DefinedUseInfiniteQueryResultTData, TError条件是initialData已设置第 189 行与第 344 行的重载分别接收UndefinedInitialDataInfiniteOptions与通用UseInfiniteQueryOptions返回UseInfiniteQueryResultTData, TError详见 useInfiniteQuery 官方函数文档。从调用方看你只需要写一次useInfiniteQuery({...})TypeScript 会依据选项对象里是否存在initialData自动选出正确的重载与结果类型无需任何手动注解。无 initialData 时的类型守卫示例import { useInfiniteQuery } from tanstack/preact-query function Projects() { const { data, isPending, isError, error, fetchNextPage, hasNextPage, isFetching } useInfiniteQuery({ queryKey: [projects], queryFn: ({ pageParam }) fetchProjects(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, }) // 必须先做守卫data 才会从 undefined 被收窄为有值类型 if (isPending) return Loading... if (isError) return spanError: {error.message}/span return ( ul {data.pages.map((page) page.projects.map((project) li key{project.id}{project.name}/li), )} /ul button onClick{() fetchNextPage()} disabled{!hasNextPage || isFetching} {hasNextPage ? Load More : Nothing more to load} /button / ) }与之相对配置了initialData的用法返回DefinedUseInfiniteQueryResult可直接解构data.pages渲染示例见 useInfiniteQuery 函数文档中的 initialData 用例。五、与 Query / Suspense 相关结果类型的边界该类型属于 Preact 结果类型族理解它与兄弟类型的关系有助于避免误用类型别名适用 Hookdata是否可为undefined定义位置UseQueryResultuseQuery无 initialData可为undefinedtypes.ts#L324-L327UseInfiniteQueryResultuseInfiniteQuery无 initialData可为undefinedtypes.ts#L364-L367DefinedUseInfiniteQueryResultuseInfiniteQuery有 initialData恒有值types.ts#L376-L379UseSuspenseInfiniteQueryResultuseSuspenseInfiniteQuery恒有值Suspense 语义data必达types.ts#L388-L394其中UseSuspenseInfiniteQueryResult相当于在DefinedUseInfiniteQueryResult基础上移除了isPlaceholderData字段Suspense 模式下永不渲染占位数据可对照阅读 DefinedUseInfiniteQueryResult 与 UseSuspenseInfiniteQueryResult 的文档。若你想在共享选项时预先把类型固定下来而不依赖 Hook 调用点的推导官方推荐使用 infiniteQueryOptions 帮助函数它返回 UseInfiniteQueryOptions 结构可同时传给useInfiniteQuery与命令式 APIqueryClient.infiniteQuery。六、无限查询结果的特有字段超越普通 useQueryUseInfiniteQueryResult展开后除继承QueryObserverBaseResultdata、error、status、isPending、isFetching、refetch等外还通过InfiniteQueryObserverBaseResult追加了无限分页专属成员见 packages/query-core/src/types.ts#L904-L944字段类型含义fetchNextPage(options?) PromiseInfiniteQueryObserverResultTData, TError拉取下一页即调用端加载更多fetchPreviousPage(options?) PromiseInfiniteQueryObserverResultTData, TError拉取上一页hasNextPageboolean依据getNextPageParam判断是否还有下一页hasPreviousPageboolean依据getPreviousPageParam判断是否还有上一页isFetchingNextPageboolean是否正在拉取下一页isFetchingPreviousPageboolean是否正在拉取上一页isFetchNextPageErrorboolean拉取下一页失败首次加载失败不算isFetchPreviousPageErrorboolean拉取上一页失败而data内部则是InfiniteData形态types.ts#L210-L213export interface InfiniteDataTData, TPageParam unknown { pages: ArrayTData // 每页数据按页序累积 pageParams: ArrayTPageParam // 与 pages 一一对应的页参数cursor }渲染时通常用data.pages.map(page ...)平铺所有已加载页页参数data.pageParams主要供调试或二次请求时使用。注意这 8 个无限查询字段即使处于pending/error状态也始终存在它们定义于所有分支共同继承的BaseResult上因此可在加载态之外安全调用fetchNextPage等命令式函数。七、结合 useBaseQuery返回值在运行时如何产生从 packages/preact-query/src/useInfiniteQuery.ts#L361-L369 的实现看useInfiniteQuery并未自建状态机而是把InfiniteQueryObserver作为 observer 类型传入内部公共实现useBaseQueryexport function useInfiniteQuery( options: UseInfiniteQueryOptions, queryClient?: QueryClient, ) { return useBaseQuery( options, InfiniteQueryObserver as typeof QueryObserver, queryClient, ) }运行时的结果对象即运行态的UseInfiniteQueryResult由InfiniteQueryObserver在tanstack/query-core内部计算并发布Preact 层只负责订阅、跟踪tracking与触发重渲染。这正是「类型在 preact-query 中重导出、行为在 query-core 中实现」的分层体现也让fetchNextPage的返回值类型与结果对象类型在引用上形成闭环。需要特别留意官方文档的提醒命令式获取如fetchNextPage可能干扰默认的自动 refetch 行为导致数据过期。因此应仅在响应用户操作时调用这些函数或加上hasNextPage !isFetching之类的守卫参考 useInfiniteQuery 函数文档的 Remarks 段落。如果你想在首次加载后并发提前预取后续页应优先考虑usePrefetchInfiniteQuery函数文档。八、实战模式滚动到底部自动加载、禁用态与可选链结合类型语义下面三种是 useInfiniteQuery 函数文档 中给出的典型消费模式1. IntersectionObserver 无限滚动用哨兵元素sentinel观察是否进入视口配合hasNextPage !isFetching守卫触发fetchNextPageimport { useInfiniteQuery } from tanstack/preact-query import { useEffect, useRef } from preact/hooks function Projects() { const { data, isPending, isError, error, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } useInfiniteQuery({ queryKey: [projects], queryFn: ({ pageParam }) fetchProjects(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, }) const sentinelRef useRefHTMLDivElement(null) useEffect(() { const sentinel sentinelRef.current if (sentinel null || !hasNextPage || isFetching) return const observer new IntersectionObserver(([entry]) { if (entry?.isIntersecting) fetchNextPage() }) observer.observe(sentinel) return () observer.disconnect() }, [hasNextPage, isFetching, fetchNextPage]) if (isPending) return Loading... if (isError) return spanError: {error.message}/span return ( ul {data.pages.map((page) page.projects.map((project) li key{project.id}{project.name}/li), )} /ul div ref{sentinelRef}{isFetchingNextPage ? Loading more... : null}/div / ) }2. 用 skipToken 代替 enabled: false 表达禁用态当依赖参数尚未就绪时将skipToken作为queryFn查询会被安全地跳过。此时由于返回类型仍属于UseInfiniteQueryResult需要借助isLoading而非isPending区分「禁用中」与「真正加载中」并且data需走可选链import { skipToken, useInfiniteQuery } from tanstack/preact-query function Comments({ postId }: { postId: string | undefined }) { const { data, isLoading, isError, error } useInfiniteQuery({ queryKey: [post, postId, comments], queryFn: postId ! null ? ({ pageParam }) fetchComments(postId, pageParam) : skipToken, initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, }) if (postId null) return Select a post if (isLoading) return Loading... if (isError) return spanError: {error.message}/span return ( ul {data?.pages.map((page) page.comments.map((c) li key{c.id}{c.text}/li))} /ul ) }3. 何时不需要守卫initialData / Suspense / 预取当使用initialData返回DefinedUseInfiniteQueryResult、useSuspenseInfiniteQuery或通过queryClient.ensureInfiniteQueryData等预取 API 后类型上data恒有值组件可以放心直接访问data.pages而不必先做isPending分支——类型系统已经从源头排除了undefined。更多面向 Preact 的分页配置initialPageParam、getNextPageParam、getPreviousPageParam、maxPages与 fetch 行为说明可继续阅读指南 infinite-queries.md 以及 UseInfiniteQueryOptions 接口文档若想复用同一份选项到命令式 API参见 infiniteQueryOptions。小结UseInfiniteQueryResultTData, TError本质是InfiniteQueryObserverResult在 Preact 适配层的重导出它通过五分支可辨识联合把「无initialData时data在 pending 阶段为undefined」这一运行时事实建模进了类型系统。使用时牢记两点即可其一访问data.pages/data.pageParams前先做isPending/isError守卫其二配置了initialData时类型会自动切换为DefinedUseInfiniteQueryResult从而获得无守卫的data访问体验。【免费下载链接】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),仅供参考
返回列表