React Query 实战要点与踩坑复盘
React Query 实战要点与踩坑复盘(含 SSR 首屏注水场景)
本文整理了在列表/分页场景中使用 TanStack Query(React Query)的关键概念、常见坑位与修复策略,并给出一套可直接落地的实践模板,覆盖 SSR 首屏注水、切换类目/分页的过渡体验、Query Key 设计 等问题。
1)核心概念精炼
-
staleTime:数据多久后被判定为“陈旧”(stale)。陈旧意味着会触发后台刷新的机会,并不清除缓存。默认0(拿到就算陈旧)。 -
cacheTime(v4) /gcTime(v5):缓存保留时长;超过时间且无人订阅时被回收。与是否刷新无直接关系。 -
initialData:向 query 注入一份“已成功的数据”,会记录updatedAt。如果staleTime足够长,这份数据可能被视为“新鲜”,从而不触发请求。 -
placeholderData:仅用于占位渲染,不改变数据新鲜度。keepPreviousData是常用策略,用于翻页/切换时保持上一屏的数据避免闪白。
2)典型坑位与修复
坑 A:处处给 initialData + 非零 staleTime → 不发请求
-
现象:切换类目或分页后,新
queryKey也拿到了initialData,且在staleTime内被视为“新鲜”,queryFn不执行。 -
修复(三选一)
-
只在“首屏那个 key”注水;
-
非首屏统一
staleTime: 0或refetchOnMount: 'always'; -
传
initialDataUpdatedAt: 0,让注水数据立刻“陈旧”,马上刷新。
-
坑 B:切换时「旧/SSR 数据被蒙层覆盖」
-
原因:蒙层由
isFetching控制,而新 key 又有initialData,所以先显示注水数据,再盖“加载中”。 -
修复:
-
过渡期用
placeholderData: keepPreviousData; -
蒙层用
isRefetching(仅“已有数据的二次请求”才盖层); -
或首屏不盖层,仅切换时盖。
-
坑 C:queryKey 里对数组使用 sort() 造成原地修改
-
问题:
subIds.sort()会修改原数组,带来副作用。 -
修复:使用不变式:
[...subIds].sort().join(',')或subIds.toSorted().join(',')。
坑 D:分页按钮逻辑只看总页数
-
正确:
-
已知总页数:
showNext = page < totalPages; -
只有总条数:
showNext = page * pageSize < totalCount。
-
坑 E:透明度隐藏 overlay 但不卸载
-
现象:
opacity-0仍在 DOM,视觉“还在”。 -
修复:条件渲染(卸载),或用动画库淡出后再卸载。
3)推荐实践:商品列表查询(含 SSR 注水)
export function useProductsQuery(opts: { tenant: { tenantId: string; tenantName: string }; catId: number | null; subIds: number[]; page: number; sortTag: SortValue; // 仅用于“首屏对应的 key”注水 initialProducts?: GetProductsOutput; }) { const { tenant, catId, subIds, page, sortTag, initialProducts } = opts; const key = qk.products( tenant.tenantId, catId, subIds, page, sortTag ); // 依据业务定义“首屏 key” const isSSRKey = catId === null && page === 1 && sortTag === SortValue.Default && subIds.length === 0; return useQuery({ queryKey: key, queryFn: () => fetchProducts(tenant, catId, subIds, page, sortTag), // 只在首屏 key 注水;其余场景不要给 initialData initialData: isSSRKey ? initialProducts : undefined, // 若希望注水后仍立刻刷新,可加: // initialDataUpdatedAt: isSSRKey ? Date.now() : 0, // 切换类目/分页保留上一屏,避免闪白 placeholderData: keepPreviousData, // 首屏可给新鲜期,其余立刻陈旧(促发请求) staleTime: isSSRKey ? 60_000 : 0, // 确保非首屏挂载时一定拉数据 refetchOnMount: isSSRKey ? false : 'always', }); }
UI 加载策略建议
-
首屏骨架:
isLoading -
切换/刷新时覆盖层:
isRefetching(避免首屏也被蒙层盖住) -
光标:仅在 overlay 出现时加
cursor-progress,或在外层容器根据isRefetching动态添加
4)Query Key 设计守则
-
仅包含影响结果的维度(如
tenantId / catId / subIds / page / sortTag)。 -
稳定序列化:数组先拷贝再排序再
join;对象挑字段或使用稳定哈希;勿放函数/非序列化值。 -
统一通过
qk.*生成,避免手写拼错。
5)参数与默认值建议
-
staleTime:首屏可适度放宽(减少抖动),非首屏设0以确保切换时及时拉新。 -
refetchOnWindowFocus:配合staleTime使用,避免频繁刷新。 -
retry:对明显业务失败可降为false/1。 -
select:在 hook 内做轻量映射,减少组件重渲染。 -
版本差异:v5 将
cacheTime改名为gcTime;keepPreviousData通过placeholderData实现。
结语
把 注水边界(仅首屏)、新鲜期与刷新策略(staleTime/refetchOnMount)、过渡显示(placeholderData/keepPreviousData)、UI 加载信号(isLoading vs isRefetching)、稳定键值(Query Key) 这几件事理顺,React Query 在复杂的类目/筛选/分页组合场景会稳定且可预期。后续可将这套模式封装成 usePagedQuery,在项目中复用。

浙公网安备 33010602011771号