服务端状态管理(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,垃圾回收时间)如何协同?

很多候选人混淆这两个时间参数,它们实际控制着缓存生命周期的两个不同阶段:

  1. staleTime(数据保鲜期,默认 0ms):决定数据从“新鲜(Fresh)”变为“陈旧(Stale)”的时间。只要数据处于 Fresh 状态,组件重新挂载、窗口重新聚焦或网络重连时,100% 直接返回内存缓存,绝不发起任何网络请求。
  2. 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 }
}

同分类其他题目