TanStack Query 在 SvelteKit 中如何用 load 函数预取数据并传给 QueryClientProvider?
【免费下载链接】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
如果你的 SvelteKit 项目用@tanstack/svelte-query(Svelte Query)管理服务器状态,SSR 场景下最常见的诉求就是:页面 HTML 还没到浏览器之前,就在服务端把数据取好,随QueryClient一起交给客户端,组件挂载时不再发起第一次请求。官方文档 SSR and SvelteKit 给出了两种方式,本文的主路径是其中更适合 SvelteKit 的方式——在服务端load函数里用queryClient.query()预取数据,把整个QueryClient经load传给QueryClientProvider。参考实现可直接对照仓库中的 SSR 示例,该示例基于@tanstack/svelte-query^6.1.48、@sveltejs/kit^2.57.1、Svelte 5。
先决设置:在服务端禁用 query 的自动执行
SvelteKit 默认以 SSR 方式渲染路由。文档明确要求:如果不关闭服务端执行,查询会在 HTML 已经发给客户端之后继续在服务器上异步执行。推荐做法是在QueryClient的defaultOptions中使用$app/environment导出的browser模块:
const queryClient = new QueryClient({ defaultOptions: { queries: { enabled: browser, }, }, })enabled: browser只影响createQuery等 hook 的行为;文档特别指出,它不会禁用queryClient.query(),而下面的服务端预取正是靠后者完成。
第一步:在 +layout.ts 的 load 中创建并返回 QueryClient
在根布局的 load 函数里新建QueryClient,作为 load 的返回值的一部分交给子路由和页面。官方示例的+layout.ts如下(示例源码):
import { QueryClient } from '@tanstack/svelte-query' import { browser } from '$app/environment' import type { LayoutLoad } from './$types' export const load: LayoutLoad = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { enabled: browser, staleTime: 60 * 1000, }, }, }) return { queryClient } }文档中的版本没有staleTime,官方示例额外配置了staleTime: 60 * 1000,按需保留即可。关键点只有一个:QueryClient实例必须在服务端load中创建并通过返回值向下游传递,页面级load才能拿到同一个实例往里面塞数据。
第二步:在 +layout.svelte 中用 QueryClientProvider 包一层
在根布局组件里从data.queryClient取出上一步的实例,传给QueryClientProvider(文档给出的+layout.svelte):
<script lang="ts"> import { QueryClientProvider } from '@tanstack/svelte-query' import type { LayoutData } from './$types' export let data: LayoutData </script> <QueryClientProvider client={data.queryClient}> <slot /> </QueryClientProvider>示例项目运行在 Svelte 5 runes 模式下,写法等价,改为const { data, children } = $props()并渲染{@render children()}。示例还在 provider 内挂载了<SvelteQueryDevtools />(来自@tanstack/svelte-query-devtools),用于查看缓存状态,属于可选项,不参与预取流程。
注意版本差异:文档示例使用export let data: LayoutData(Svelte 4 风格),示例项目使用$props()(Svelte 5 runes),两者二选一,与项目 Svelte 版本保持一致即可。
第三步:在页面 load 中用 queryClient.query 预取数据
页面级+page.ts通过parent()拿到布局传来的queryClient,调用queryClient.query()完成服务端预取。文档给出的代码:
export async function load({ parent, fetch }) { const { queryClient } = await parent() // You need to use the SvelteKit fetch function here await queryClient .query({ queryKey: ['posts'], queryFn: async () => (await fetch('/api/posts')).json(), }) .catch(noop) }两个必须注意的点:
- 文档原样注释了queryFn 里必须使用 SvelteKit 的
fetch(即load函数的fetch参数),不能直接调globalThis.fetch; - 调用后统一接
.catch(noop),noop从@tanstack/svelte-query导入,官方示例 同样如此:
import { noop } from '@tanstack/svelte-query' import type { PageLoad } from './$types' import { api } from '$lib/api' export const load: PageLoad = async ({ parent, fetch }) => { const { queryClient } = await parent() await queryClient .query({ queryKey: ['posts', 10], queryFn: () => api(fetch).getPosts(10), }) .catch(noop) }示例里api(fetch)只是把 SvelteKit 的fetch注入到一个请求封装中(api.ts),本质仍是“queryFn 使用 SvelteKit 提供的 fetch”。
第四步:组件用相同 queryKey 调用 createQuery
预取完成后,页面组件用与服务端query调用相同的queryKey创建查询。文档给出的+page.svelte:
<script lang="ts"> import { createQuery } from '@tanstack/svelte-query' // This data is cached by query in +page.ts so no fetch actually happens here const query = createQuery(() => ({ queryKey: ['posts'], queryFn: async () => (await fetch('/api/posts')).json(), })) </script>由于+page.ts里的queryClient.query()已经把数据写进了缓存,客户端组件挂载时命中缓存,文档结论是:页面渲染后不会发生初次 fetch,且 query 缓存保留了dataUpdatedAt等完整信息。对照示例,Posts.svelte组件使用与页面 load 一致的queryKey: ['posts', limit]创建查询,并通过client.getQueryData(['post', post.id])判断某条数据是否已缓存(Posts.svelte)。
如何验证预取生效
按示例项目的方式启动验证(examples/svelte/ssr):
npm run dev(示例package.json中dev脚本为vite dev,也可用npm run preview预览npm run build的构建产物。)
判断依据以文档描述为准:
- 页面首屏数据来自服务端预取的缓存,客户端不产生该 query 的初次请求;
- 组件内
posts.status不会停留在'pending'(示例组件按pending/error/正常三态渲染列表); - 若挂载了
SvelteQueryDevtools,可在开发者工具面板中查看该 query 已处于缓存状态。
限制与注意事项
- 文档明确说明:
queryClient.query()这套方案不能用于+page.server.ts/+layout.server.ts的 load 函数(文档解释为:配合 TanStack Query 使用的 API 本就需要对浏览器完全暴露)。如果你的数据必须放在 server-only 的 load 里,只能走下面的initialData替代方案。 - 服务端预取的 query 与客户端
createQuery必须使用相同的queryKey,否则缓存无法命中,客户端仍会发起请求。 - 服务端
queryFn里务必使用load传入的 SvelteKitfetch,这是文档的显式要求。
替代方案:用 initialData 直接传数据
文档同时提供了更轻量的方式:页面load里请求数据并以return { posts }返回,组件把它传给createQuery的initialData:
// src/routes/+page.ts export async function load() { const posts = await getPosts() return { posts } }<script> import { createQuery } from '@tanstack/svelte-query' import type { PageData } from './$types' export let data: PageData const query = createQuery(() => ({ queryKey: ['posts'], queryFn: getPosts, initialData: data.posts, })) </script>文档给出的取舍依据:
- 优点:设置最简,且同时兼容
+page.ts/+layout.ts与+page.server.ts/+layout.server.ts两种 load 函数; - 缺点:如果
createQuery在更深的组件里,需要逐层把initialData传下去;同一查询在多处使用时每处都要传;且无法知道数据在服务端何时取回,dataUpdatedAt与是否需要 refetch 的判断基于页面加载时间而非服务端取数时间。
如果预取目标是“数据进入 QueryClient 缓存、全局任意组件免 prop-drilling 可用”,选本文主路径的queryClient.query()方案;如果数据只被单页使用且逻辑简单,initialData更省事。完整可运行代码见 examples/svelte/ssr,更多背景见 SSR and SvelteKit 文档。
【免费下载链接】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),仅供参考