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号
浙公网安备 33010602011771号