服务端状态管理(TanStack Query / SWR)与客户端状态库(Zustand / Pinia)有何本质区别?staleTime 与 gcTime 如何协同?
在现代前端架构中,服务端状态(Server State) 与 客户端状态(Client State) 被明确拆分为两个正交维度:把客户端全局 Store 从繁重的异步 API 缓存管理中解放出来,交由 TanStack Query(React Query / Vue Query)或 SWR 处理,通常能直接砍掉项目中 70% 以上的全局 Store 代码。
一、 服务端状态 vs 客户端状态的本质差异
| 对比维度 | 客户端状态(Zustand / Pinia / Redux) | 服务端状态(TanStack Query / SWR) |
|---|---|---|
| 数据所有权(Ownership) | 前端独占、同步可控,修改即刻生效且永远是最新的 | 存储在远端数据库,只是前端在某一时刻拿到的异步快照,随时可能被其他用户修改而过期(Stale) |
| 核心痛点 | 跨组件通信、精确控制重渲染、持久化到 localStorage |
请求去重(Deduplication)、缓存保鲜期(staleTime)、垃圾回收(gcTime)、失败指数退避重试、分页与无限滚动缓存合并 |
二、 高频考点:staleTime(保鲜期)与 gcTime(原 cacheTime,垃圾回收时间)如何协同?
很多候选人混淆这两个时间参数,它们实际控制着缓存生命周期的两个不同阶段:
staleTime(数据保鲜期,默认0ms):决定数据从“新鲜(Fresh)”变为“陈旧(Stale)”的时间。只要数据处于 Fresh 状态,组件重新挂载、窗口重新聚焦或网络重连时,100% 直接返回内存缓存,绝不发起任何网络请求。gcTime(非活跃缓存垃圾回收期,默认5 分钟):当使用该queryKey的**所有组件都已卸载(Unmount,引用计数归零)**后,缓存数据在内存中保留多久才被彻底清除。在gcTime到期前若用户再次切回该页面,会先立即展示旧缓存数据(零白屏),同时若已超过staleTime则在后台静默发起重验证(Stale-While-Revalidate)。
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function useProjectDetail(projectId: string) {
const queryClient = useQueryClient()
// 1. 查询并配置保鲜期与垃圾回收期
const query = useQuery({
queryKey: ['project', projectId], // 数组依赖键,projectId 变化自动切换缓存槽位
queryFn: ({ signal }) =>
fetch(`/api/projects/${projectId}`, { signal }).then((r) => r.json()),
staleTime: 60 * 1000, // 1 分钟内视为绝对新鲜,切页面不重发请求
gcTime: 10 * 60 * 1000, // 组件卸载后缓存保留 10 分钟,后退秒开
})
// 2. 修改数据后精准使关联缓存失效(Invalidation),自动触发后台重新拉取
const updateMutation = useMutation({
mutationFn: (patch: { name: string }) =>
fetch(`/api/projects/${projectId}`, { method: 'PATCH', body: JSON.stringify(patch) }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['project', projectId] })
}
})
return { ...query, updateProject: updateMutation.mutate }
}