尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Solid Query 中的滚动位置恢复(Scroll Restoration):缓存、placeholderData 与路由协作完整指南

Solid Query 中的滚动位置恢复(Scroll Restoration):缓存、placeholderData 与路由协作完整指南 Solid Query 中的滚动位置恢复Scroll Restoration缓存、placeholderData 与路由协作完整指南【免费下载链接】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在浏览器中回到一个此前访问过的页面时页面会精确地恢复到离开前的滚动位置这一能力被称为滚动位置恢复Scroll Restoration。然而当 SPA 全面转向客户端数据获取之后这一体验出现了普遍退化。本文将基于 TanStack QuerySolid Query官方文档中《Scroll Restoration》指南的内容结合当前仓库GitHub_Trending/qu/query的源码与测试说明 Solid Query 虽然不自带滚动位置恢复实现却通过缓存与placeholderData移除了它在 SPA 中最主要的破坏者从而让回到上一页立即渲染、布局稳定、滚动位置可被可靠恢复成为开箱即用的事实。背景客户端数据获取时代滚动恢复为何失灵传统浏览器行为里用户点击链接离开、再通过后退按钮返回一个已访问页面时浏览器会依据历史记录中的滚动锚点将页面滚动回上次停留的位置。这是浏览器层面对文档滚动状态的原生记忆能力。但当应用转为客户端渲染SPA后页面的内容不再由浏览器从网络重新加载而是由 JavaScript 在路由切换时动态渲染。这时滚动位置能否恢复取决于页面在返回瞬间是否以与离开时一致的布局与高度渲染出来。如果页面在渲染数据前先显示 loading随后数据到达、DOM 高度发生变化浏览器保存的滚动锚点与恢复时机就对不上——表现为回到列表页却停在顶部或跳到错误位置。TanStack Query 文档对这一问题的定性非常明确数据获取逻辑本身并不会导致滚动位置丢失真正破坏恢复的是因重新获取数据而引发的 UI 重置refetch-induced UI resets。相关内容可参见 Scroll Restoration 指南源文档其内容经由ref机制被 Solid 框架版本 复用两者由同一份正文与少量框架化替换生成。TanStack Query 的定位不是恢复的实现者而是障碍清除者需要特别澄清一个易混淆点TanStack Query 并不自己实现滚动位置恢复。它不读取也不写回window.scrollY不监听 history 的 popstate。它的价值在于消除了恢复机制失效的最大诱因。具体机制链条如下缓存保留旧数据当用户离开一个查询页面时查询数据不会消失而是留在缓存中见下文 gcTime 规则。返回时同步渲染再次渲染该查询时Solid Query 能同步从缓存取回数据页面即刻以与离开前相同的 DOM 结构与高度渲染出来。可选placeholderData即使新页面需要新的查询参数也可以借助placeholderData先展示上一份数据作为占位避免出现空白或 loading 引发的布局抖动。交由路由层恢复滚动由于布局在返回瞬间即稳定路由框架自带的 ScrollRestoration 能力或基于 history 记录滚动位置的轻量自定义方案都可以在正确的时机把页面滚回原处。也就是说TanStack Query 负责页面以正确形态就位滚动恢复本身由路由/浏览器历史层负责两者分工协作而非重叠竞争。为什么开箱即用缓存数据可以同步读取文档原文强调开箱即用下滚动位置恢复对所有查询类型——包括分页查询与无限滚动查询——都能正常工作。其根本原因只有一个查询结果是带缓存的且查询渲染时能够同步读取到缓存。这背后是 TanStack Query 在缓存设计上的一个关键取舍查询结果在 JavaScript 引擎内部以内存对象形式保存而非异步从持久层读取因此当查询 Observer 重新挂载并命中缓存时第一次渲染就能拿到完整数据根本不需要等待一个异步 tick。UI 因此不会经历先空/loading、后填充的切换过程。这个行为在 Solid Query 的测试中亦有体现例如 QueryClientProvider.test.tsx 验证了 QueryClient 上gcTime: Infinity这类全局默认配置如何被持久化到查询的选项中。决定成败的三项缓存配置要让返回页面立即以稳定布局渲染成立需要保证数据在返回时仍存在于缓存中且未被当作垃圾回收。以下三项默认值直接决定这一点。gcTime默认 5 分钟的垃圾回收窗口TanStack Query 对已无活动观察者active observer的查询标记为 inactive并在默认5 分钟1000 * 60 * 5毫秒后将其从缓存中垃圾回收。因此只要用户在 5 分钟内执行离开 → 返回操作且查询未被回收滚动恢复就一直成立。该默认值及全部aggressive but sane默认约定详见 Important Defaults 指南Solid 框架版。若你的业务场景需要更长的恢复窗口可在 QueryClient 上全局调整也可在单个查询上局部覆盖import { QueryClient } from tanstack/solid-query const queryClient new QueryClient({ defaultOptions: { queries: { // 默认 1000 * 60 * 5按需延长例如半小时 gcTime: 1000 * 60 * 30, }, }, })注意gcTime: 0意味着查询一旦失去活动订阅就立即回收这会直接让滚动恢复失效。Solid Query 的测试中对这一边界也有覆盖例如 useQuery.test.tsx 验证了gcTime: 0时重新挂载后缓存中的查询如何被重新拾起。staleTime区分新鲜与过期的刷新节流阀默认情况下缓存中的数据一经读取就被视为stale过期导致挂载、窗口重新聚焦、网络重连时自动触发后台重新获取。后台重取并不清空当前 UI 数据页面仍显示旧数据、布局不变但如果你希望返回时连后台闪烁都不发生可以为查询设置合理的staleTimestaleTime: 2 * 60 * 10002 分钟内读缓存、不触发任何重取staleTime: Infinity永不因过期触发重取直到手动 invalidatestaleTime: static更强约束连手动invalidateQueries也无法让其重取。值得强调的是后台重取与滚动恢复并不冲突——因为重取发生在页面已渲染、布局已稳定之后属于就地更新不会把 DOM 推倒重建。structuralSharing防止不必要的引用变化引发重渲染默认开启的结构共享structural sharing会逐层比较新旧响应若内容未真正变化则保留原数据引用。这进一步保证了返回页面时不会因无关的状态跳变引发组件重渲染与布局偏移为滚动恢复提供稳定执行环境。placeholderData让新页面也能瞬间呈现成为可能纯靠 gcTime 命中缓存只适用于回到同一个查询键。对于进入新参数页面如切换分页页码、进入详情页再返回的场景文档推荐使用placeholderData提供占位数据使页面在真实数据到达前就拥有一份可渲染内容。完整用法见 Placeholder Query Data 指南Solid 框架版常见三种形态如下。保持上一份数据作为占位翻页/筛选场景最常用对应 Solid Query 导出的keepPreviousDataimport { useQuery, keepPreviousData } from tanstack/solid-query function Posts() { const postsQuery useQuery(() ({ queryKey: [posts, page], queryFn: () fetchPosts(page), placeholderData: keepPreviousData, })) }这一行为在 Solid Query 的测试中被专门验证 useQuery.test.tsx 与 useInfiniteQuery.test.tsx 均断言设置了placeholderData: keepPreviousData后新查询会保留上一份数据。从其他查询的缓存中派生占位数据const queryClient useQueryClient() const blogPostQuery useQuery(() ({ queryKey: [blogPost, id], queryFn: () fetch(/blogPosts/${id}), placeholderData: () queryClient.getQueryData([blogPosts])?.find((p) p.id id), }))若占位数据计算较重还可以用createMemo记忆化占位值避免每次渲染重复执行。占位数据被标记为 placeholder不会写入持久缓存也不会造成真实查询结果的错乱。分页与无限查询跨页返回同样成立文档特别点明滚动恢复对所有查询含分页、无限查询开箱即用其含义是每一页的查询结果都以独立查询键如[posts, page]或无限查询的 pages 形式整体留在缓存中。返回列表页时此前加载过的每一页数据都能同步取出列表高度与滚动容器尺寸即刻还原为浏览器与路由层的滚动恢复提供了正确前提。无限滚动场景的更多细节可参考 Infinite Queries 指南源文档Solid 框架版的对应文件为 infinite-queries.md其中说明无限查询在渲染时即可同步取回所有已缓存 pages。在 Solid 应用中的落地清单总结一个在 Solid Solid Query 应用中让滚动恢复稳定工作的可操作清单滚动位置恢复交给路由层使用路由框架自带的历史滚动恢复能力或在history/路由切换事件中自行保存并恢复各路径对应的scrollY。TanStack Query 不提供也不应承担这部分职责。不要为返回后不闪烁而过早清缓存保持默认gcTime5 分钟或按需延长只有内存敏感的页面才考虑缩短或设gcTime: 0但要接受滚动恢复随之失效的代价。为列表页配置placeholderData/keepPreviousData让翻页、筛选、详情返回等场景在真实数据到达前即有稳定内容渲染是消除内容高度跳变的关键手段。利用staleTime平滑刷新对不常变化的数据设置较长staleTime减少返回瞬间的后台重取噪音注意区分Infinity与static在手动失效语义上的差异。保持结构共享开启避免自定义比较逻辑破坏引用稳定性仅在 JSON 不兼容数据或极端大响应带来的性能问题出现时再考虑关闭或提供自定义共享函数。结语与进一步阅读滚动位置恢复是路由层恢复机制 数据层稳定渲染共同作用的结果。Solid Query 用同步可读的缓存、5 分钟默认回收窗口、placeholderData/keepPreviousData与结构共享清除了 SPA 中导致恢复失效的根源——重取引发的 UI 重置让应用在返回上一页时能立即以稳定的布局就位从而把恢复动作交还给路由层并保证其可靠执行。仓库内可继续深入阅读的相关材料Scroll Restoration 官方指南React/Solid 等共享源文档Solid 框架版 Scroll Restoration 指南入口Important Defaults缓存回收、stale 与重取默认约定Placeholder Query Dataplaceholder 三种形态与记忆化Infinite Queries 指南源码验证useQuery 的 placeholderData 测试、useInfiniteQuery 的 keepPreviousData 测试【免费下载链接】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),仅供参考
返回列表