在 Svelte 5 中使用 createInfiniteQuery 实现无限滚动数据加载
在 Svelte 5 中使用 createInfiniteQuery 实现无限滚动数据加载【免费下载链接】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/querycreateInfiniteQuery是 TanStack Svelte Querytanstack/svelte-query提供的核心 Hook用于管理分页/无限列表数据它把加载更多下拉分页游标翻页这类重复逻辑抽象为声明式状态并内置缓存、后台刷新与去重。本文基于本仓库docs/framework/svelte/reference/functions/createInfiniteQuery.md与packages/svelte-query的源码实现系统讲解其函数签名、响应式参数、返回值语义并给出Load More 按钮与IntersectionObserver 自动加载两种完整可运行的 Svelte 5 实战方案以及通过infiniteQueryOptions复用选项的最佳实践。一、函数签名与类型参数createInfiniteQuery在 packages/svelte-query/src/createInfiniteQuery.ts 中定义是createQuery的无限分页变体。其完整签名如下function createInfiniteQuery TQueryFnData unknown, TError DefaultError, TData InfiniteDataTQueryFnData, TQueryKey extends QueryKey QueryKey, TPageParam unknown, ( options: Accessor CreateInfiniteQueryOptionsTQueryFnData, TError, TData, TQueryKey, TPageParam , queryClient?: AccessorQueryClient, ): CreateInfiniteQueryResultTData, TError1. 五个类型参数类型参数默认值含义TQueryFnDataunknownqueryFn返回的单页数据结构TErrorDefaultError即Error查询失败时的错误类型TDataInfiniteDataTQueryFnData最终暴露给组件的数据结构即{ pages, pageParams }TQueryKeyQueryKey即readonly unknown[]查询键类型必须可序列化TPageParamunknown分页参数类型页码、游标等由getNextPageParam返回与createQuery的关键差异在于多出的TPageParam以及TData默认为InfiniteDataTQueryFnData。InfiniteData定义于tanstack/query-core结构为interface InfiniteDataTData, TPageParam { pages: TData[] // 已加载的所有页面数据 pageParams: TPageParam[] // 与 pages 一一对应的分页参数 }2. 参数说明optionsAccessorCreateInfiniteQueryOptions...。Accessor本质是() T见 packages/svelte-query/src/types.ts即选项必须包在一个函数里这样每次 Svelte 响应式依赖变化时该函数被重新求值选项即自动更新。queryClient?AccessorQueryClient。传入自定义QueryClient不传则使用最近上下文中由QueryClientProvider提供的实例。3. 重载与返回值类型源码中createInfiniteQuery声明了三个重载编译器会根据是否提供initialData自动选择传入DefinedInitialDataInfiniteOptionsinitialData已设置→ 返回DefinedCreateInfiniteQueryResultTData, TErrordata永远非undefined传入UndefinedInitialDataInfiniteOptionsinitialData为undefined或缺省→ 返回CreateInfiniteQueryResultTData, TError通用重载接收CreateInfiniteQueryOptions对应 types.ts 中CreateInfiniteQueryOptions与CreateInfiniteQueryResult的定义。返回值是当前查询结果并额外附带fetchNextPage/fetchPreviousPage/hasNextPage/hasPreviousPage用于在分页中前进与后退。二、返回值详解无限查询的状态与操作createInfiniteQuery返回的CreateInfiniteQueryResult继承自InfiniteQueryObserverResult除常规状态外重点字段如下字段类型说明dataInfiniteDataTData, TPageParam \| undefined{ pages, pageParams }页面按加载顺序排列data.pagesTData[]已抓取的页面数组data.pageParamsTPageParam[]与pages一一对应的分页参数数组isPendingboolean首次加载且无缓存数据isSuccessboolean已有dataisError/errorboolean/TError错误状态与错误对象isFetchingboolean任意抓取进行中含后台刷新isFetchingNextPageboolean正在抓取下一页isFetchingPreviousPageboolean正在抓取上一页hasNextPagebooleangetNextPageParam返回了非undefined值hasPreviousPagebooleangetPreviousPageParam返回了非undefined值fetchNextPage()() Promise...抓取下一页fetchPreviousPage()() Promise...抓取上一页refetch()() Promise...重新抓取所有页面hasNextPage的判断逻辑位于 packages/query-core/src/infiniteQueryBehavior.ts只有当getNextPageParam(options, data)返回非null/undefined时才为true。这意味着是否还有更多页完全由你的getNextPageParam返回值决定——返回undefined即代表到达末尾。三、核心配置项initialPageParam 与 getNextPageParam无限查询的核心配置是三个字段底层行为由tanstack/query-core的infiniteQueryBehaviorpackages/query-core/src/infiniteQueryBehavior.ts驱动initialPageParam必填。第一页使用的分页参数类型为TPageParam。它会被放入data.pageParams[0]。getNextPageParam(lastPage, allPages, lastPageParam, allPageParams)基于已加载的最后一页计算下一页参数返回undefined表示没有更多页源码实现。getPreviousPageParam(firstPage, allPages, firstPageParam, allPageParams)可选。基于第一页计算上一页参数返回undefined表示没有上一页源码实现。抓取流程概括为见 infiniteQueryBehavior.ts加载下一页时优先使用pageParams[pageParams.length - 1]即上一页的页码作为入参调用getNextPageParam得到新参数加载上一页时则基于oldPageParams[0]调用getPreviousPageParam。此外还有一个常用配置maxPages当页面数量超过该值时infiniteQueryBehavior会通过addTo(data.pages, page, maxPages)丢弃最旧的页面源码用于限制无限增长列表的内存占用可结合只保留最近 N 页的产品需求使用。四、实战一Load More 按钮这是createInfiniteQuery最经典的用法——点击按钮加载下一页。文档给出的完整示例createInfiniteQuery.md如下script langts import { createInfiniteQuery } from tanstack/svelte-query const query createInfiniteQuery(() ({ queryKey: [projects], queryFn: ({ pageParam }) fetchProjects(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, })) /script {#if query.isPending} Loading... {:else if query.isError} spanError: {query.error.message}/span {:else} ul {#each query.data.pages as page} {#each page.projects as project (project.id)} li{project.name}/li {/each} {/each} /ul button onclick{() query.fetchNextPage()} disabled{!query.hasNextPage || query.isFetching} {query.isFetchingNextPage ? Loading more... : query.hasNextPage ? Load More : Nothing more to load} /button {/if}要点说明queryFn: ({ pageParam }) ...queryFn接收的上下文对象中带有pageParam字段其值由 TanStack Query 根据initialPageParam和getNextPageParam自动注入你无需手动维护当前页码。双层{#each}query.data.pages是页面数组外层遍历页面、内层遍历页面内的列表项。按钮状态机isFetchingNextPage时显示 Loading more...hasNextPage为false时显示 Nothing more to load 并禁用按钮用query.isFetching兜底防止并发点击。仓库中的真实可运行示例位于 examples/svelte/load-more-infinite-scroll/src/lib/LoadMore.svelte它调用 SWAPI 接口分页拉取星球数据getNextPageParam从响应的nextURL 中解析出下一页游标const query createInfiniteQuery(() ({ queryKey: [planets], queryFn: ({ pageParam }) fetchPlanets({ pageParam }), initialPageParam: 1, getNextPageParam: (lastPage) { if (lastPage.next) { const nextUrl new URLSearchParams(new URL(lastPage.next).search) const nextCursor nextUrl.get(page) if (nextCursor) return nextCursor } return undefined // 没有 next 说明已到最后一页 }, }))该示例同样用isPending/error/isSuccess三态渲染并在按钮上以isFetching、hasNextPage驱动文案切换可作为直接拷贝运行的参考。五、实战二IntersectionObserver 滚动自动加载当用户滚动到列表底部时自动加载下一页是无限滚动的标准实现。文档给出了借助哨兵元素sentinel与IntersectionObserver的完整方案createInfiniteQuery.mdscript langts import { createInfiniteQuery } from tanstack/svelte-query const query createInfiniteQuery(() ({ queryKey: [projects], queryFn: ({ pageParam }) fetchProjects(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, })) let sentinel: HTMLDivElement | undefined $state() $effect(() { if (sentinel null || !query.hasNextPage || query.isFetching) return const observer new IntersectionObserver(([entry]) { if (entry?.isIntersecting) query.fetchNextPage() }) observer.observe(sentinel) return () observer.disconnect() }) /script {#if query.isPending} Loading... {:else if query.isError} spanError: {query.error.message}/span {:else} ul {#each query.data.pages as page} {#each page.projects as project (project.id)} li{project.name}/li {/each} {/each} /ul div bind:this{sentinel}/div {/if}实现要点let sentinel: HTMLDivElement | undefined $state()bind:this用 Svelte 5 的$state声明哨兵元素引用bind:this在元素挂载后赋值。$effect中创建并清理 observer$effect的依赖追踪会自动把sentinel、query.hasNextPage、query.isFetching纳入依赖回调里return () observer.disconnect()实现卸载时清理避免内存泄漏。守卫条件sentinel null || !query.hasNextPage || query.isFetching时直接 return防止对已加载完成的列表继续观察、或在抓取进行中重复触发。六、选项复用infiniteQueryOptions当同一组无限查询选项需要在多个组件或命令式 API如queryClient.infiniteQuery、queryClient.prefetchInfiniteQuery之间共享时可以使用infiniteQueryOptions包裹选项见 docs/framework/svelte/reference/functions/infiniteQueryOptions.md。其实现位于 packages/svelte-query/src/infiniteQueryOptions.ts本质是返回传入的 options 并注入QueryKeyWithDataTag让queryKey携带推导出的数据类型从而在queryClient.infiniteQuery等命令式调用中获得完整类型推断。场景一initialData跳过首屏加载态。传入initialData: { pages: [], pageParams: [] }后即使首屏抓取失败空列表仍保持可见错误以旁路形式展示script langts import { infiniteQueryOptions, createInfiniteQuery } from tanstack/svelte-query const projectsOptions infiniteQueryOptions({ queryKey: [projects], queryFn: ({ pageParam }) fetchProjects(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, initialData: { pages: [], pageParams: [] }, }) const query createInfiniteQuery(() projectsOptions) /script {#if query.isError} spanError: {query.error.message}/span {/if} ul {#each query.data.pages as page} {#each page.projects as project (project.id)} li{project.name}/li {/each} {/each} /ul场景二参数化工厂。为每个postId复用同一份选项定义结合Accessor实现响应式切换script langts import { infiniteQueryOptions, createInfiniteQuery } from tanstack/svelte-query let { postId }: { postId: string } $props() const commentsOptions (postId: string) infiniteQueryOptions({ queryKey: [post, postId, comments], queryFn: ({ pageParam }) fetchComments(postId, pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, }) const query createInfiniteQuery(() commentsOptions(postId)) /script {#if query.isPending} Loading... {:else if query.isError} spanError: {query.error.message}/span {:else} ul {#each query.data.pages as page} {#each page.comments as comment (comment.id)} li{comment.text}/li {/each} {/each} /ul {/if}类型层面infiniteQueryOptions同样按是否提供initialData分为两个重载设置initialData时走DefinedInitialDataInfiniteOptions分支否则走UndefinedInitialDataInfiniteOptions分支infiniteQueryOptions.ts与createInfiniteQuery的重载策略保持一致。七、底层原理createInfiniteQuery 如何运行createInfiniteQuery的运行时实现非常精简createInfiniteQuery.ts——它把工作委托给createBaseQuery并传入InfiniteQueryObserver作为观察者export function createInfiniteQuery( options: AccessorCreateInfiniteQueryOptions, queryClient?: AccessorQueryClient, ) { return createBaseQuery( options, InfiniteQueryObserver as typeof QueryObserver, queryClient, ) }createBaseQuery位于 packages/svelte-query/src/createBaseQuery.svelte.ts其内部流程揭示了 Svelte 版本响应式设计的核心响应式解析 QueryClientconst client $derived(useQueryClient(queryClient?.()))——传入自定义 client 则用之否则从最近上下文获取。合并默认选项resolvedOptions $derived.by(() client.defaultQueryOptions(options()))——每次options()内依赖变化如postId变化都会重新求值实现选项的自动响应式更新。创建/重建 Observer首次通过$state创建new InfiniteQueryObserver(client, resolvedOptions)当client变化时通过watchChanges(..., pre, ...)在 effect 前重建 observer。订阅与乐观结果$effect中执行observer.subscribe(...)回调内用observer.getOptimisticResult(resolvedOptions)计算最新结果并通过createRawRef写入 Svelte 状态从而驱动模板重新渲染_optimisticResults被设置为optimistic保证 UI 能立即响应fetchNextPage()等操作而不必等网络返回。恢复isRestoring处理恢复期间不订阅恢复结束后的首次更新由watchChanges兜底调用update(createResult())避免遗漏中间状态。这也是为什么createInfiniteQuery的 options 必须包在函数Accessor中只有函数形式才能被$derived.by追踪依赖并重算从而把 Svelte 5 的细粒度响应式与 TanStack Query 的观察者模式无缝衔接。八、小结createInfiniteQuery面向分页/无限列表场景返回的data.pages聚合所有已加载页面fetchNextPage/fetchPreviousPage负责翻页hasNextPage/hasPreviousPage指示边界。三个必配项缺一不可queryKey缓存标识、initialPageParam首页参数、getNextPageParam计算下一页返回undefined终止。options 必须以Accessor函数形式传入以获得响应式更新自定义QueryClient可通过第二参数传入。滚动加载用IntersectionObserver 哨兵元素 $effect清理即可优雅实现需要限制内存时使用maxPages。多组件/命令式 API 共享选项时用infiniteQueryOptions包裹可获得完整类型推断并支持initialData首屏直出。相关文件速览createInfiniteQuery 实现、createBaseQuery 基类、infiniteQueryOptions、无限查询核心行为、完整示例 LoadMore.svelte。【免费下载链接】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),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →