Apollo Client 3.2.0 fetchPolicy问题:查询被重复调用两次
这种缓存引发重复请求的坑我之前踩过,咱们一步步来拆解原因和解决办法:
先搞清楚问题根源
Apollo Client的缓存是靠查询的唯一标识来管理的,这个标识由「查询文档的标准化字符串」+「变量的序列化结果」共同生成。正常来说,两个组件传入不同的type变量,生成的标识应该完全不同,不会互相干扰。但你遇到的情况是用cache-and-network或network-only时出现重复请求,no-cache却正常,核心原因大概率和缓存的读取/更新逻辑或变量初始化时机有关。
第一步:先确认重复请求的细节
先打开Apollo Client DevTools(Chrome官方扩展),切换到「Network」标签,盯着重复的请求看:
- 看看两次请求的变量参数是否完全一致?如果一致,说明同一个查询被触发了两次(比如组件重复渲染导致
useQuery重复执行); - 如果变量不一致,那大概率是组件初始化时
type参数有变化(比如从undefined或者默认值变成目标值),导致发起了两次请求。
针对性的解决思路
1. 排查缓存键是否真的唯一
Apollo的缓存键生成有时候会因为查询文档的格式(比如空格、换行)或者变量序列化的问题出现意外冲突,你可以手动验证一下:
import { print } from '@apollo/client'; // 把查询文档转成标准化字符串 const standardQueryStr = print(getChartData); // 分别序列化两个组件的变量 const varStr1 = JSON.stringify({ year: 2024, type: 'your-first-type' }); const varStr2 = JSON.stringify({ year: 2024, type: 'your-second-type' }); // 打印两个查询的缓存键,应该完全不同 console.log(`组件1的缓存键: ${standardQueryStr}${varStr1}`); console.log(`组件2的缓存键: ${standardQueryStr}${varStr2}`);
如果这两个键相同,那说明你的变量传递肯定有问题(比如两个组件的type其实是同一个值);如果不同,那继续往下看。
2. 解决缓存归一化带来的冲突
如果你的查询返回的节点没有唯一id字段,Apollo会自动生成一个基于字段的默认id。如果两个不同type的查询返回的节点结构完全一致,可能会被当成同一个节点存入缓存,导致后续的查询触发重新请求。
你可以在初始化Apollo Cache的时候,自定义节点的id生成规则,把type变量包含进去:
import { InMemoryCache, defaultDataIdFromObject } from '@apollo/client'; const cache = new InMemoryCache({ dataIdFromObject: (object) => { // 针对first_series_data的节点,生成包含type的唯一id if (object.__typename === 'FirstSeriesDataNode') { // 注意:这里需要你的查询返回type字段,否则拿不到 return `${object.__typename}:${object.week}:${object.type}`; } // 针对second_series_data的节点 if (object.__typename === 'SecondSeriesDataNode') { return `${object.__typename}:${object.date}:${object.type}`; } // 其他节点用默认规则 return defaultDataIdFromObject(object); } });
3. 避免变量未就绪时发起请求
如果组件在初始化时year或type还没拿到(比如从父组件的异步状态传递过来),可以用skip选项跳过无效的初始请求:
useQuery(getChartData, { variables: { year, type }, fetchPolicy: 'cache-and-network', skip: !year || !type // 变量不全时不发起请求 });
4. 手动控制请求时机(用useLazyQuery)
如果不需要组件一挂载就请求数据,可以用useLazyQuery手动触发请求,确保只在变量准备好时发起一次:
const [fetchChartData] = useLazyQuery(getChartData, { fetchPolicy: 'cache-and-network' }); useEffect(() => { // 变量都就绪了再发起请求 if (year && type) { fetchChartData({ variables: { year, type } }); } }, [year, type, fetchChartData]);
5. 考虑升级Apollo Client版本
你用的3.2.0是比较旧的版本,当时缓存系统还有一些已知的bug(比如多根字段查询的缓存更新逻辑)。如果以上方案都无效,可以尝试升级到3.10.x左右的稳定版本,大概率能解决一些隐性的缓存问题。
内容的提问来源于stack exchange,提问作者Alin

