十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

TanStack Solid Query 的 useInfiniteQuery:无限滚动与分页加载的完整实战指南

TanStack Solid Query 的 useInfiniteQuery:无限滚动与分页加载的完整实战指南 TanStack Solid Query 的 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/query导读useInfiniteQuery是 TanStack Solid Querytanstack/solid-query提供的无限查询 Hook专门用于加载更多分页、游标、无限滚动场景例如 Feed 流、聊天记录、商品列表等需要逐页追加数据的界面。它基于useQuery的全部能力构建额外引入了页码参数pageParam、双向翻页下一页/上一页与页数上限maxPages机制。读完本文你将掌握useInfiniteQuery的完整 API、全部配置项与返回属性、底层实现原理以及如何在 SolidJS 组件中实现一个可靠的无限滚动列表。本文以 useInfiniteQuery.md 为骨架结合 solid-query 源码 与 query-core 的无限查询实现 进行纵深讲解确保每个结论都有文档与源码双重依据。一、为什么需要 useInfiniteQuery与 useQuery 的本质差异普通useQuery一次只获取一份数据而无限查询需要面对三个useQuery无法优雅处理的问题分页参数需要记忆与累积每次加载新页时需要用上一次返回的游标/页码作为下一次请求的参数并且所有已加载的页要拼接成一个列表是否还有下一页/上一页需要推导该状态由getNextPageParam/getPreviousPageParam的返回值决定是触发加载更多按钮或 Intersection Observer 的依据页数需要受控数据无限增长会拖垮内存与渲染maxPages用于裁剪过旧/过新的页。从源码看useInfiniteQuery在 Solid 层的实现非常薄——它本质上是把InfiniteQueryObserver交给共享的useBaseQuery基座来处理// packages/solid-query/src/useInfiniteQuery.ts export function useInfiniteQueryTQueryFnData, TError, TData, TQueryKey, TPageParam( options: UseInfiniteQueryOptionsTQueryFnData, TError, TData, TQueryKey, TPageParam, queryClient?: AccessorQueryClient, ): UseInfiniteQueryResultTData, TError { return useBaseQuery( createMemo(() options()), InfiniteQueryObserver as typeof QueryObserver, queryClient, ) as UseInfiniteQueryResultTData, TError }可以看到options以 Accessor函数形式传入并被createMemo包裹这意味着它可以响应式地跟踪 SolidJS 信号的变化与 useQuery 的Reactive Options设计一致而真正的无限逻辑全部封装在InfiniteQueryObserver与infiniteQueryBehavior中见下文底层原理。二、API 签名总览useInfiniteQuery的完整调用形式如下来自 useInfiniteQuery.md 的 API 示例const { fetchNextPage, fetchPreviousPage, hasNextPage, hasPreviousPage, isFetchingNextPage, isFetchingPreviousPage, ...result } useInfiniteQuery(() ({ queryKey, queryFn: ({ pageParam }) fetchPage(pageParam), initialPageParam: 1, ...options, getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) lastPage.nextCursor, getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) firstPage.prevCursor, }))函数签名与useQuery一致都是两个参数第一个参数AccessorInfiniteQueryOptions——返回配置对象的函数用于响应式跟踪见 useInfiniteQuery.ts第二个参数可选AccessorQueryClient——自定义 QueryClient缺省时使用最近上下文QueryClientProvider中的实例。类型层面UseInfiniteQueryResult就是 query-core 的InfiniteQueryObserverResult其数据类型固定为InfiniteDataTData, TPageParam结构为{ pages: TData[]; pageParams: TPageParam[] }见 solid-query/src/types.ts。提示源码中还提供了infiniteQueryOptions辅助函数见 infiniteQueryOptions.ts它本身是恒等函数原样返回 options主要作用是利用 TypeScript 类型推断把data属性标注为InfiniteData在把配置对象抽离到组件外时获得更精确的类型推导。三、Options无限查询专属配置项useInfiniteQuery的选项与 useQuery hook 完全一致额外增加以下 5 个配置项。3.1 queryFn —— 请求函数接收 pageParam必填除非已通过 QueryClient 定义了默认查询函数defaultQueryFn。查询用来请求数据的函数接收一个 QueryFunctionContext。必须返回一个 Promiseresolve 出数据或 throw 出错误。数据不能是undefined。在无限查询中QueryFunctionContext上多出了两个关键字段由 infiniteQueryBehavior.ts 构造const queryFnContext { client, queryKey, pageParam: param, // 本次要获取的页码/游标 direction: previous ? backward : forward, // 获取方向 meta, }因此queryFn: ({ pageParam }) fetchPage(pageParam)中解构出的pageParam正是由getNextPageParam/getPreviousPageParam计算出来并注入的。3.2 initialPageParam —— 首页参数必填。获取第一页时使用的默认 pageParam。在 infiniteQueryBehavior.ts 中首次拉取没有 direction、没有旧页时const param currentPage 0 ? (oldPageParams[0] ?? options.initialPageParam) : getNextPageParam(options, result)即第一页优先使用已缓存的第一页参数如initialData带来的否则使用initialPageParam后续每一页用getNextPageParam推导。3.3 getNextPageParam —— 推导下一页参数必填。签名(lastPage, allPages, lastPageParam, allPageParams) TPageParam | undefined | null。当查询收到新数据时此函数接收无限列表的最后一页、全部页数组以及对应的 pageParam 信息。它应返回单个变量该变量会作为pageParam传入 queryFn 的 context如queryFn: ({ pageParam }) ...。返回undefined或null表示没有下一页。典型用法游标分页getNextPageParam: (lastPage) lastPage.nextCursor,底层实现中该函数由getNextPageParam辅助函数调用且仅在已有页时才会被调用见 infiniteQueryBehavior.tsfunction getNextPageParam(options, { pages, pageParams }) { const lastIndex pages.length - 1 return pages.length 0 ? options.getNextPageParam(pages[lastIndex], pages, pageParams[lastIndex], pageParams) : undefined }注意hasNextPage正是getNextPageParam返回值不为null/undefined的直接映射见 infiniteQueryBehavior.ts所以只要它返回了有效值hasNextPage即为true。3.4 getPreviousPageParam —— 推导上一页参数可选与getNextPageParam不同文档未标记为 Required从源码getPreviousPageParam?.的可选调用也能印证。签名(firstPage, allPages, firstPageParam, allPageParams) TPageParam | undefined | null。当查询收到新数据时此函数接收无限列表的第一页、全部页数组及 pageParam 信息。返回的单个变量将作为pageParam传入 queryFn 的 context。返回undefined或null表示没有上一页。它服务于向上翻页如聊天记录往上加载场景。hasPreviousPage在未定义该函数或返回空值时均为false见 infiniteQueryBehavior.ts。3.5 maxPages —— 页数上限类型number | undefined默认undefined。无限查询数据中最多存储的页数。当达到最大页数后再获取新页会导致pages数组头部或尾部被移除具体取决于获取方向向后加载fetchNextPage时移除第一页addToEnd见 infiniteQueryBehavior.ts向前加载fetchPreviousPage时移除最后一页addToStart。若为undefined或0页数不受限制。若maxPages 0必须正确定义getNextPageParam与getPreviousPageParam以便在需要时两个方向都能拉取页面。页的追加/裁剪由utils中的addToEnd/addToStart完成对pages和pageParams同步操作保证两者长度一致。使用建议对只增不减的 Feed 流可保持undefined对内存敏感或需要固定窗口如仅保留最近 N 页的场景设置具体数值。要兼顾向上加载与向下加载同时启用maxPages时务必把两个 pageParam 函数都配齐。四、Returns返回属性详解useInfiniteQuery返回一个 SolidJS StoreStoreInfiniteQueryObserverResult其属性与 useQuery hook 一致含status、isPending、isSuccess、isError、dataUpdatedAt、error、failureCount、fetchStatus、refetch等差异点在于data不是普通数据而是{ pages, pageParams }结构见 4.1新增fetchNextPage/fetchPreviousPage/hasNextPage/hasPreviousPage等 6 个方向性属性isRefetching与isRefetchError的语义有细微差别见 4.8、4.9。这些新属性的计算集中在一个地方——InfiniteQueryObserver.createResult见 infiniteQueryObserver.ts它以state.fetchMeta?.fetchMore?.directionforward/backward区分当前请求的方向。4.1 data.pages 与 data.pageParamsdata.pages: TData[]——包含所有已加载页的数组。data.pageParams: unknown[]——与pages一一对应的 pageParam 数组第 i 个 page 是用第 i 个 pageParam 请求来的。两个数组在每次拉页时同步更新见 3.5 中的addTo逻辑因此用data.pages渲染列表、用data.pageParams做去重或调试都很方便。注意data本身是 SolidJS Resource见 useQuery.md 对data: ResourceTData的说明。如果data在Suspense下被访问且数据尚不可用会触发 Suspense 边界这在 SSR/流式渲染场景中要留意。4.2 fetchNextPage / fetchPreviousPagefetchNextPage: (options?: FetchNextPageOptions) PromiseUseInfiniteQueryResult——获取下一页。fetchPreviousPage: (options?: FetchPreviousPageOptions) PromiseUseInfiniteQueryResult——获取上一页。options.cancelRefetch: boolean为true默认时重复调用fetchNextPage会每次都触发queryFn即使上一次调用尚未 resolve且前一次调用的结果会被忽略。为false时在第一次调用 resolve 之前重复调用不产生任何效果。从源码看这两个方法只是给内部fetch挂上方向标记见 infiniteQueryObserver.tsfetchNextPage(options?: FetchNextPageOptions) { return this.fetch({ ...options, meta: { fetchMore: { direction: forward } } }) } fetchPreviousPage(options?: FetchPreviousPageOptions) { return this.fetch({ ...options, meta: { fetchMore: { direction: backward } } }) }infiniteQueryBehavior根据该direction决定使用getNextPageParam还是getPreviousPageParam计算本次要请求的参数以及新页追加到头部还是尾部见 infiniteQueryBehavior.ts。4.3 hasNextPage / hasPreviousPagehasNextPage: boolean——通过getNextPageParam判断若其返回值不为null/undefined则为true。hasPreviousPage: boolean——通过getPreviousPageParam判断且当该函数未定义时恒为false。实现见 infiniteQueryBehavior.ts。它们是加载更多按钮的天然开关常用写法Show when{state.hasNextPage !state.isFetchingNextPage} button onClick{() state.fetchNextPage()}加载更多/button /Show4.4 isFetchingNextPage / isFetchingPreviousPage当通过fetchNextPage获取下一页的过程中为true。当通过fetchPreviousPage获取上一页的过程中为true。推导逻辑见 infiniteQueryObserver.tsconst isFetchingNextPage isFetching fetchDirection forward const isFetchingPreviousPage isFetching fetchDirection backward它们通常用来显示底部加载中的占位提示或阻止重复点击。4.5 isFetchNextPageError / isFetchPreviousPageErrorisFetchNextPageError: boolean——获取下一页失败时为true。isFetchPreviousPageError: boolean——获取上一页失败时为true。同样由方向判定infiniteQueryObserver.tsconst isFetchNextPageError isError fetchDirection forward const isFetchPreviousPageError isError fetchDirection backward4.6 isRefetching无限查询专属语义只要**后台重取background refetch**进行中即为true不包含初始pending、也不包含下一页/上一页的获取。等价于isFetching !isPending !isFetchingNextPage !isFetchingPreviousPage。源码中的实现infiniteQueryObserver.tsisRefetching: isRefetching !isFetchingNextPage !isFetchingPreviousPage,对比 useQuery 的isRefetchingisFetching !isPending无限查询把方向性获取排除在外避免加载更多时误触发正在刷新的 UI 状态。4.7 isRefetchError无限查询专属语义查询在重取页面非首次、非方向性获取时失败为true。源码中排除了方向性获取错误infiniteQueryObserver.tsisRefetchError: isRefetchError !isFetchNextPageError !isFetchPreviousPageError,五、完整的实战示例下面是一个基于 SolidJS tanstack/solid-query的游标分页列表示例覆盖了响应式配置、加载更多按钮与完整的状态分支。它综合了 useInfiniteQuery.md 的 API 示例、useQuery.md 的组件写法Show/For/createSignal并参考了 useInfiniteQuery.test.tsx 中的调用方式如fetchNextPage由按钮点击触发。import { createSignal, Show, For } from solid-js import { useInfiniteQuery } from tanstack/solid-query interface Page { items: Array{ id: number; name: string } nextCursor: number | null } function fetchPage(pageParam: number): PromisePage { return fetch(/api/items?cursor${pageParam}).then((res) { if (!res.ok) throw new Error(Failed to fetch) return res.json() }) } function ItemList() { const [enabled, setEnabled] createSignal(true) const state useInfiniteQuery(() ({ queryKey: [items], enabled: enabled(), // 响应式配置queryKey / queryFn 内部都可以读取信号 queryFn: ({ pageParam }) fetchPage(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextCursor, // 返回 null 即没有下一页 })) return ( div Show when{state.isLoading} div首次加载中…/div /Show Show when{state.isError} div加载失败: {state.error?.message}/div /Show Show when{state.isSuccess} ul For each{state.data?.pages.flatMap((p) p.items)} {(item) li{item.name}/li} /For /ul {/* 加载更多按钮hasNextPage 控制显隐isFetchingNextPage 防重复点击 */} Show when{state.hasNextPage !state.isFetchingNextPage} button onClick{() state.fetchNextPage()}加载更多/button /Show Show when{state.isFetchingNextPage} div加载下一页中…/div /Show Show when{state.isFetchNextPageError} div下一页加载失败/div /Show /Show /div ) }5.1 实现要点响应式选项useInfiniteQuery(() ({ ... }))的函数体在 SolidJS 响应式作用域内执行enabled()、filter()等信号变化会自动触发查询重建与 useQuery 的 Reactive Options 一致。测试中也验证了选项变化后查询能正确重新执行。游标为null表示到底getNextPageParam返回null时hasNextPage变为false按钮自动隐藏测试用例should set hasNextPage to false if getNextPageParam returns undefined验证了该行为见 useInfiniteQuery.test.tsx。错误分支isFetchNextPageError专门标识加载更多失败可用于在按钮位置展示重试入口测试用例should return the correct states when fetchNextPage fails对此有完整覆盖。cancelRefetch 的取舍默认true下快速连点会重复请求并忽略旧结果若想一次加载中禁止再次请求传{ cancelRefetch: false }——测试should not cancel an ongoing fetchNextPage request when another fetchNextPage is invoked if cancelRefetch: false is used验证了该语义。5.2 与 Suspense / ErrorBoundary 结合由于data是 SolidJS Resource可配合Suspense与ErrorBoundary声明式处理首屏加载与错误与 useQuery 的 Suspense 用法 相同注意设置throwOnError: trueErrorBoundary fallback{(err) div出错了: {err.message}/div} Suspense fallback{div加载中…/div} ItemList / /Suspense /ErrorBoundary六、底层原理一次 fetchNextPage 调用发生了什么把上面的 API 串起来一次fetchNextPage()的完整链路是Solid 层useInfiniteQuery把optionsAccessor交给useBaseQuery见 useBaseQuery.ts后者创建InfiniteQueryObserver实例并用createResource包装观察结果返回带 Proxy 的 Store对data做了 Resource 访问转发见 useBaseQuery.ts。Observer 层fetchNextPage()以meta.fetchMore.direction forward调用fetchinfiniteQueryObserver.ts。Behavior 层infiniteQueryBehavior的onFetch读取direction与旧数据有旧页且带方向 → 用getNextPageParam(options, oldData)算出本次param然后fetchPage(oldData, param)把新页追加到尾部addToEnd并受maxPages裁剪无方向首次/重取→ 从initialPageParam开始循环拉页直到达到remainingPagesinfiniteQueryBehavior.ts。结果合并createResult基于fetchDirection推导出isFetchingNextPage、isFetchNextPageError等 6 个方向性属性并修正isRefetching/isRefetchError的语义infiniteQueryObserver.ts。响应式传播Store 更新后组件中读取state.data、state.isFetchingNextPage的地方自动重渲染。这套设计的好处方向状态不放在 Solid 层而是内聚在 query-core 的 fetchMeta 中因此useInfiniteQuery与useQuery的响应式基座完全共享行为差异全部由InfiniteQueryObserversetOptions时标记_type infinite见 infiniteQueryObserver.ts与 Behavior 承担Solid 包装层保持极薄。七、注意事项与最佳实践命令式翻页可能干扰默认重取fetchNextPage这类命令式调用会与默认的重取行为如refetchOnWindowFocus、refetchInterval相互干扰可能导致展示过期数据。请仅在用户动作的响应中调用点击、滚动触底回调或加上hasNextPage !isFetching之类的条件再调用。hasNextPage 依赖服务端返回getNextPageParam的返回值直接决定hasNextPage因此服务端必须在每页响应中携带可判空的游标字段如nextCursor: number | null。maxPages 与双向加载启用maxPages且需要向上/向下都能翻时务必同时定义getNextPageParam与getPreviousPageParam否则裁剪方向会失去对侧可拉取的参数来源。不要在 render 中直接调用 fetch 方法fetchNextPage应挂在事件回调onClick或createEffect中避免渲染期间触发副作用。服务端渲染注意SSR 期间useBaseQuery会把retry置为false、throwOnError置为true见 useBaseQuery.ts且fetchNextPage/fetchPreviousPage等函数在序列化时会被置空、水合后恢复见 useBaseQuery.ts因此 SSR 输出中不要依赖这些函数。类型提示若把配置对象抽到组件外可借助infiniteQueryOptions包装以获得data的InfiniteData类型推导见 infiniteQueryOptions.ts。八、扩展阅读useQuery hookuseInfiniteQuery的选项与返回值以它为基底包含enabled、select、staleTime、gcTime、refetchInterval等全部通用配置的详细说明。Query KeysqueryKey的哈希与自动更新机制。Query FunctionsQueryFunctionContext中各字段含无限查询注入的pageParam、direction的完整说明。Default Query Function省略queryFn时的默认函数定义方式。Network Mode离线/弱网下的取数行为与fetchStatus语义。核心实现useInfiniteQuery.ts、infiniteQueryObserver.ts、infiniteQueryBehavior.ts。测试用例useInfiniteQuery.test.tsx覆盖加载更多、失败状态、cancelRefetch、hasNextPage 推导等行为。【免费下载链接】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),仅供参考
返回列表