资讯动态

TanStack Query Angular 实战:用 initialData 为 injectQuery 预置初始数据,跳过首屏 Loading

发布时间:2026/9/7 18:38:14 来源:尧图企业网站定制
TanStack Query Angular 实战用 initialData 为 injectQuery 预置初始数据跳过首屏 Loading【免费下载链接】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在 Angular 中使用 TanStack Querytanstack/angular-query的injectQuery时查询挂上就立刻转圈是常见的首屏体验问题。本文围绕 Angular 框架文档 initial-query-data 展开系统讲解如何通过initialData、staleTime、initialDataUpdatedAt三个配置项为查询预置初始数据、精确控制何时重新拉取并进一步演示懒求值的initialData函数、从缓存派生初始数据、以及按数据新鲜度做条件预置等进阶用法。读完本文你能在 Angular 服务或组件中完整落地有缓存就秒出、过期才回源的数据初始化方案并理解其背后tanstack/query-core的状态初始化与陈旧度判定机制。给查询预先准备数据的三种途径TanStack Query 提供了声明式与命令式两类提前备数据的思路声明式在injectQuery的配置中直接提供initialData当该 key 在缓存中为空时用它预填充缓存从而跳过初始 loading 状态命令式通过queryClient的 API 在需要之前手动操作缓存——用queryClient.query预取数据或用queryClient.setQueryData直接把数据放入缓存。相关 API 详见 QueryClient 参考文档。声明式的initialData适合应用里已经持有这份数据的场景例如路由状态、父查询结果、SSR 注入等。用initialData预置数据并跳过 loading 状态当你的应用已经拥有某份数据时可以直接把它交给initialData查询创建后不会进入 loading 状态result.data()立即可用result injectQuery(() ({ queryKey: [todos], queryFn: () fetch(/todos), initialData: initialTodos, }))在 Angular 中这段代码写在服务或组件实例内即可执行injectQuery依赖注入上下文参见 inject-query.ts。一个值得注意的类型层细节injectQuery针对提供了initialData的重载会返回DefinedCreateQueryResultTData, TError而非普通的CreateQueryResult见 inject-query.ts#L65-L78。也就是说一旦声明了initialDataTypeScript 会推断出数据一定存在data信号的类型不再是可空——这正是跳过初始 loading在类型系统上的体现。重要initialData会被持久化写入缓存。因此不推荐用它塞入占位符、部分数据或不完整数据如果只是想在加载期间显示占位内容应使用 placeholderQueryData 方案。staleTime与initialDataUpdatedAt决定预置数据多新鲜initialData默认被视为刚刚拉取的最新数据因此它会直接影响staleTime的判定。围绕这一点有三种典型配置配置一只有initialData没有staleTime。默认staleTime: 0意味着数据立即可判定为陈旧因此组件或服务实例一旦创建查询会立即重新拉取——预置数据只是先展示一帧// Will show initialTodos immediately, but also immediately refetch todos // when an instance of the component or service is created result injectQuery(() ({ queryKey: [todos], queryFn: () fetch(/todos), initialData: initialTodos, }))配置二加上staleTime。数据会被视为新鲜同样长的时间仿佛是刚刚从queryFn拿到的。例如staleTime: 1000时1 秒之内即使重新挂载也不会触发 refetch只有遇到交互事件窗口聚焦、网络恢复、组件重新挂载等且数据已过新鲜期才会重新拉取// Show initialTodos immediately, but wont refetch until // another interaction event is encountered after 1000 ms result injectQuery(() ({ queryKey: [todos], queryFn: () fetch(/todos), initialData: initialTodos, staleTime: 1000, }))配置三initialDataUpdatedAt精确标注数据时间戳。如果你的initialData其实并不新鲜staleTimealone 就不够准确——这时应传入initialDataUpdatedAt一个毫秒级 JS 时间戳Date.now()同款标明这份初始数据本身的最后更新时间。注意如果你的数据源给的是 Unix 秒级时间戳需要乘以1000转换// Show initialTodos immediately, but wont refetch until // another interaction event is encountered after 1000 ms result injectQuery(() ({ queryKey: [todos], queryFn: () fetch(/todos), initialData: initialTodos, staleTime: 60 * 1000, // 1 minute // This could be 10 seconds ago or 10 minutes ago initialDataUpdatedAt: initialTodosUpdatedTimestamp, // eg. 1608412420052 }))这个选项让staleTime回归本职——定义数据需要多新——同时让查询自行决定如果initialData的时间戳已经比staleTime更老挂载时就会直接 refetch。上面的例子中数据必须在 1 分钟内是新鲜的若初始数据是 10 分钟前更新的查询会判定其过期并立即回源。如果你希望把数据当预取数据处理而不是初始数据更推荐先用queryClient的查询 API 把缓存填好这样staleTime的语义就与initialData解耦了。源码视角dataUpdatedAt是怎么初始化的从源码结构看上述行为的实现集中在tanstack/query-core的状态构造函数getDefaultState中query.ts#L745-L780const data typeof options.initialData function ? (options.initialData as InitialDataFunctionTData)() : options.initialData const hasData data ! undefined const initialDataUpdatedAt hasData ? typeof options.initialDataUpdatedAt function ? options.initialDataUpdatedAt() : options.initialDataUpdatedAt : 0 return { data, dataUpdatedAt: hasData ? (initialDataUpdatedAt ?? Date.now()) : 0, ... status: hasData ? success : pending, fetchStatus: idle, }可以印证两点其一有initialData时初始status直接是success、dataUpdatedAt默认取Date.now()即视为刚刚获取所以staleTime: 0时挂载即 refetch其二提供了initialDataUpdatedAt时它会被写入dataUpdatedAt。而陈旧度判定isStaleByTime正是基于该时间戳计算query.ts#L314-L330return !timeUntilStale(this.state.dataUpdatedAt, staleTime)——dataUpdatedAt距今超过staleTime即为陈旧查询随即在下次观察时回源。相关类型定义见 types.ts#L259-L260initialData可以是值或函数initialDataUpdatedAt可以是数字或返回数字的函数。用函数形式的initialData做懒计算如果获取初始数据的开销较大读取大缓存、解析存储、做复杂查找又不想在每次响应式执行时重复付出代价可以把initialData写成函数。该函数只在查询初始化、创建默认状态时执行一次即上面源码中getDefaultState的调用时机从而节省内存与 CPUresult injectQuery(() ({ queryKey: [todos], queryFn: () fetch(/todos), initialData: () getExpensiveTodos(), }))从其他查询的缓存派生初始数据一个常见场景你已有[todos]列表查询的缓存现在要打开某个 todo 的详情查询。与其让详情查询从零开始加载可以直接从列表缓存中找出那条记录作为详情查询的初始数据result injectQuery(() ({ queryKey: [todo, this.todoId()], queryFn: () fetch(/todos), initialData: () { // Use a todo from the todos query as the initial data for this todo query return this.queryClient .getQueryData([todos]) ?.find((d) d.id this.todoId()) }, }))带上initialDataUpdatedAt的缓存派生从缓存派生数据时源查询的数据往往已经偏旧。此时不建议用人为拉高的staleTime来骗过立即 refetch而是把源查询的dataUpdatedAt原样传给initialDataUpdatedAt让详情查询基于真实的新鲜度自行判断是否回源result injectQuery(() ({ queryKey: [todos, this.todoId()], queryFn: () fetch(/todos/${this.todoId()}), initialData: () queryClient.getQueryData([todos])?.find((d) d.id this.todoId()), initialDataUpdatedAt: () queryClient.getQueryState([todos])?.dataUpdatedAt, }))条件式初始数据旧到一定程度就不用缓存了如果源查询的数据实在太旧你宁愿干脆放弃缓存、从硬加载状态开始拉取。这时改用queryClient.getQueryState拿到完整的查询状态用state.dataUpdatedAt做新鲜度门槛判断result injectQuery(() ({ queryKey: [todo, this.todoId()], queryFn: () fetch(/todos/${this.todoId()}), initialData: () { // Get the query state const state queryClient.getQueryState([todos]) // If the query exists and has data that is no older than 10 seconds... if (state Date.now() - state.dataUpdatedAt 10 * 1000) { // return the individual todo return state.data.find((d) d.id this.todoId()) } // Otherwise, return undefined and let it fetch from a hard loading state! }, }))注意这里返回undefined是刻意为之initialData求值结果为undefined时getDefaultState会走hasData false分支状态回到pending查询进入正常的加载流程参见上文 query.ts#L758 附近的hasData逻辑。Angular 集成要点与验证依据injectQuery的实现在 inject-query.ts它通过runInInjectionContext在给定或当前注入上下文中创建QueryObserver因此示例中this.todoId()、this.queryClient这类依赖实例状态的写法在信号化的 Angular 应用中天然成立injectQuery传入的选项函数运行在响应式上下文中文档注释中说明其类似computedqueryKey、enabled等都可以引用信号initialData的求值则只发生在查询初始化时两者时机不同写懒函数时不必担心被响应式重算反复触发提供的选项类型DefinedInitialDataOptions/UndefinedInitialDataOptions定义在 query-options.ts分别对应有初始数据返回 Defined 结果和无初始数据返回普通结果两种重载行为验证可参考测试文件 inject-query.test.ts 中围绕initialData的用例以及 query-options.test-d.ts 中对initialData类型的编译期断言。小结场景配置要点效果跳过首屏 loadinginitialData立即展示数据status直接为 success立即展示但挂载即回源仅initialDatastaleTime默认 0显示一帧后立即 refetch展示并延迟回源initialDatastaleTime: 1000数据视为刚拉取1 秒内不 refetch数据本身可能偏旧initialDatastaleTimeinitialDataUpdatedAt按真实时间戳判定新鲜度过旧则挂载即回源初始数据获取昂贵initialData: () ...仅初始化时求值一次用其他查询的缓存派生getQueryData/getQueryStateinitialDataUpdatedAt继承源查询的新鲜度可加新鲜度门槛做条件预置再次强调核心原则initialData会持久化进缓存只应放真实、完整的数据占位展示请用placeholderData见 placeholder-query-data。掌握以上配置组合你就能在 Angular 应用中把初始数据 陈旧度的控制权完全握在自己手里。【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价