
Solid Query 与 Solid Suspense 集成指南开箱即用的数据挂起与 Render-as-you-fetch 预取实践【免费下载链接】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/querySolid Querytanstack/solid-query是 TanStack Query 面向 SolidJS 框架的适配层。本指南围绕 docs/framework/solid/guides/suspense.md 讲解如何将 SolidJS 原生的Suspense边界与 Solid Query 的查询结合起来无需任何额外配置只要在 Suspense 边界内读取查询的data组件就会在数据就绪前自动挂起并展示 fallback在此基础上进一步讨论 Fetch-on-render 与 Render-as-you-fetch 两种数据加载模型并借助预取把加载时机前移到路由回调与用户交互事件中。读完本文你将掌握 Solid Query 挂起模式的开箱用法、挂起与错误边界配合的规则以及从渲染时才取数升级到先取数再渲染的实战路径。为什么在 Solid Query 中使用 Suspense在 SolidJS 中Suspense是一个用于协调异步资源resource的组件边界。当一个位于Suspense内的组件在渲染期间读取了尚未就绪的异步资源时该边界不会输出组件内容而是渲染fallback待资源就绪后Solid 会自动恢复出真实内容。这意味着开发者可以把数据加载中这一状态完全交给框架处理组件代码中不再需要手写if (isPending) return ...这类分支。Solid Query 的查询结果天然就是这种异步资源的形态因此它与 Solid 的 Suspense 的Solid Query 与 React Query 的重要差异一节中官方直接声明只要你在Suspense边界内访问查询数据Suspense 对查询就是开箱即用的。这一点与 React Query 中需要显式开启suspense: true的使用方式有本质区别是迁移或新接入时需要最先建立的认知。基础用法用 Suspense 包裹可挂起组件要让 Solid Query 的查询挂起生效最核心的一步是把读取了查询数据的可挂起组件用 Solid 提供的Suspense组件包裹起来并为它提供一个fallback。当查询仍在加载时用户会看到 fallback一旦data就绪组件树便会自动渲染。import { Suspense } from solid-js Suspense fallback{LoadingSpinner /} SuspendableComponent / /Suspensefallback可以是任意 UI一个加载动画组件、一段文本甚至null。实际项目中常把 fallback 设计为LoadingSpinner之类的骨架屏组件以避免加载期间出现布局跳动。接下来定义可挂起的组件本身。它与普通useQuery用法一致查询键、查询函数、以及……直接读取数据。无需设置任何开启 suspense之类的选项。import { useQuery } from tanstack/solid-query const todoFetcher async () await fetch(https://jsonplaceholder.cypress.io/todos).then((response) response.json(), ) function SuspendableComponent() { const todosQuery useQuery(() ({ queryKey: [todos], queryFn: todoFetcher, })) // 在 Suspense 边界内直接访问 todosQuery.data // 会自动触发挂起直到数据就绪 return divData: {JSON.stringify(todosQuery.data)}/div }注意上述代码的两个细节useQuery的入参是一个函数() ({ ... })。这是 Solid Query 与 React Query 的 API 差异Solid 采用细粒度响应式参数函数允许查询键等字段在响应式作用域内被追踪。这一点在 quick-start.md 中也有专门对比。是否触发挂起取决于data被读取的位置。下面这段来自 quick-start 的对比最能说明问题import { For, Suspense } from solid-js function Example() { const query useQuery(() ({ queryKey: [todos], queryFn: fetchTodos, })) return ( div {/* ✅ 在 Suspense 边界内读取 data会触发 loading fallback */} Suspense fallback{Loading...} For each{query.data}{(todo) div{todo.title}/div}/For /Suspense {/* ❌ 在 Suspense 边界外读取 data不会触发 loading fallback */} For each{query.data}{(todo) div{todo.title}/div}/For /div ) }挂起的底层原理query.data 即 Solid 资源开箱即用的背后是 Solid Query 的实现设计。从源码 packages/solid-query/src/useBaseQuery.ts 可以清晰看到其挂起机制useBaseQuery在内部通过 Solid 的createResource把查询观测器QueryObserver的结果包装成了一个异步资源见 useBaseQuery.ts#L237-L270资源的读取器会在 Promise 中订阅 observer并在observerResult.isLoading时保持 pending。最终返回的是一个Proxy(state, handler)见 useBaseQuery.ts#L371-L386。当访问data属性时handler 会读取queryResourceconst handler { get(target, prop) { if (prop data) { if (state.data ! undefined) { return queryResource.latest?.data } return queryResource()?.data } return Reflect.get(target, prop) }, }也就是说读data就是读一个 Solid 资源资源处于 pending 时读取会被 Solid 捕获并让最近的Suspense边界挂起这也解释了为什么必须把读取动作放进 Suspense 边界内才有效。基于这一设计v5 起的 Solid Query 已经将suspense选项标记为deprecated。在 packages/solid-query/src/types.ts#L38-L44 的类型注释中说明useQuery/useInfiniteQuery的data本身就是一个 SolidJS resource数据加载时会自动挂起因此设置suspense: false也会被当作 no-op。换句话说只要你在边界内读取数据挂起行为就始终存在无须也无法通过旧选项关闭。与 React Query 相比这是两套框架 hook 语义差异的直接体现——React Query 需要把读取挂起建模成显式抛出的 Promise而 Solid 则靠资源读取自然完成。Suspense 下的错误处理ErrorBoundary 与 throwOnError当查询失败时挂起的资源会把错误向上抛出。因此 Suspense 模式下的错误处理通常由 Solid 的ErrorBoundary承接而非在组件里判断isError。推荐的嵌套结构是外层ErrorBoundary负责错误、内层Suspense负责加载中状态import { ErrorBoundary, Suspense } from solid-js ErrorBoundary fallback{(_err, resetSolid) ( div p数据加载失败/p button onClick{() resetSolid()}重试/button /div )} Suspense fallbackloading... SuspendableComponent / /Suspense /ErrorBoundary错误是否需要抛出到边界由throwOnError选项控制。根据 docs/framework/solid/reference/useQuery.md 的文档说明其取值语义如下默认值为false即错误作为查询状态isError/error返回不抛给边界若设置了已废弃的suspense: true默认会变为true在 SSR 期间默认强制为true与服务端渲染需要reject资源的实现一致见 useBaseQuery.ts#L133-L136 中if (isServer) { ...; defaultOptions.throwOnError true }也可以是(error, query) boolean函数按错误内容精细决定哪些错误抛给边界。对应的测试用例见 packages/solid-query/src/tests/suspense.test.tsx其中系统验证了默认suspense选项置位时错误抛给ErrorBoundary第 440-487 行throwOnError: false时不抛错正常渲染第 489-530 行throwOnError传入函数时返回值决定是否抛出第 532-623 行配合resetSolid()重置错误边界后能够重新发起请求第 272-335 行。可见 Suspense ErrorBoundary 的组合并非简单把错误丢出去而是与查询重试、边界重置形成了闭环的容错机制。Fetch-on-render 与 Render-as-you-fetch默认情况下Solid Query 的 Suspense 模式表现得像一个典型的Fetch-on-render渲染时取数方案且无需任何额外配置。其含义是当组件尝试挂载时它们会触发查询获取并挂起——但前提是这些组件已经被导入并挂载。也就是说取数的起点仍然是组件开始渲染这一时刻加载动画之前的网络耗时并没有被隐藏。如果希望进一步降低感知延迟可以把模型升级为Render-as-you-fetch边取数边渲染即在真正渲染之前就让查询先跑起来。官方在 suspense.md 中给出的建议是在路由回调routing callbacks和/或用户交互事件里实现 Prefetching让查询在对应组件被挂载之前、甚至在开始导入或挂载它们的父组件之前就启动。Solid Query 的预取实践详见同目录下的 docs/framework/solid/guides/prefetching.md那里给出了组件生命周期内的三种预取姿势使用useQuery并忽略返回值——把查询当作副作用启动适合与父查询数据强关联、几乎必然会被消费的子查询function Article(props) { const articleQuery useQuery(() ({ queryKey: [article, props.id], queryFn: getArticleById, })) // 预取评论忽略结果只为了让查询提前启动 useQuery(() ({ queryKey: [article-comments, props.id], queryFn: getArticleCommentsById, // 可选优化避免该查询变化引发多余重渲染 notifyOnChangeProps: [], })) // ...渲染 articleQuery.data }在 queryFn 内部预取——适合一旦取到文章几乎必然还要评论的场景借助queryClient.query发起内层请求const articleQuery useQuery(() ({ queryKey: [article, id], queryFn: (...args) { void queryClient .query({ queryKey: [article-comments, id], queryFn: getArticleCommentsById, }) .catch(noop) return getArticleById(...args) }, }))在 effect 中预取——同样基于queryClient.query但把启动时机放到createEffect中按需执行import { createEffect } from solid-js const queryClient useQueryClient() createEffect(() { void queryClient .query({ queryKey: [article-comments, id], queryFn: getArticleCommentsById, }) .catch(noop) })在路由层面做预取是更彻底的方案为每个路由显式声明其组件树所需的数据在路由加载器中启动请求。对于关键数据可以await阻塞路由渲染配合路由的错误处理对次要数据则用.catch(noop)先发起但不等待。完整的路由集成代码示例可继续阅读 prefetching.md 的 Router Integration 一节。挂起行为的边界场景与注意事项结合 useQuery.test.tsx 与 suspense.test.tsx 等测试可以把挂起行为的关键边界总结如下便于在真实应用中避免踩坑Suspense 模式下一个queryKey只会触发一次 queryFn测试should not call the queryFn twice when used in Suspense modesuspense.test.tsx#L143-L168验证了挂起等待与资源读取不会导致重复请求。切换查询键会重新挂起当组件通过响应式信号切换queryKey时旧数据对应查询被新查询取代Suspense 边界会再次进入 loading直到新数据就绪第 395-438 行。重新挂载会重新取数将gcTime、staleTime配置为合适值后隐藏再显示组件会依据新鲜度重新发起请求期间isFetching为 true第 337-393 行。enabled: false的查询不会触发请求即使位于 Suspense 边界内被禁用的查询也不会调用 queryFn而data保持未定义可结合Show/Switch等渲染真实内容第 625-663 行。这与 Solid 资源pending 且不可用的语义一致。不要在组件卸载后无限挂起源码中为observer 卸载早于资源加载完成的场景实现了队列化退订与 resolver 兜底逻辑见 useBaseQuery.ts#L121-L125、useBaseQuery.ts#L344-L357注释明确这是为了修复 Suspense 边界被无限期挂起的问题测试should remove query instance when component unmounted第 170-211 行验证了卸载后 observer 数归零。无限查询useInfiniteQuery同样支持挂起在 Suspense 下切换分页也会触发 loading fallback第 85-141 行后续可参考 infinite-queries.md 了解更多。补充SSR 与流式渲染视角下的 Suspense如果你在服务端渲染场景中使用 Suspense需要注意 Solid Query 针对isServer的默认策略见 useBaseQuery.ts#L127-L138服务端会将retry置为false、throwOnError置为true避免在服务器上无限重试并确保错误能通过资源 reject 传递。此外类型定义中还有一个与流式渲染相关的选项deferStream默认false置为true后服务端会等待查询解析完成再冲刷流从而避免把 loading 状态先发给客户端见 packages/solid-query/src/types.ts#L31-L38。这与资源加载、onHydrated时回填 Query Cache 的流程共同支撑了服务端 Suspense 的完整链路更深入的讨论可参考 docs/framework/solid/guides/ssr.md。小结Solid Query 把查询结果建模为 Solid 资源使Suspense集成成为默认能力在边界内读取data即自动挂起失败则交给ErrorBoundary处理。默认的 Fetch-on-render 模型零配置即可用若要获得更流畅的体验应结合 Prefetching 在路由回调或交互事件中提前发起请求过渡到 Render-as-you-fetch 模型。无论选择哪种方式queryKey切换、enabled禁用、错误边界重置、卸载清理等边界行为都已由 suspense.test.tsx 等测试覆盖可作为你实现与排错的权威参考。【免费下载链接】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),仅供参考