Apollo Client readQuery缓存有数据却返回undefined问题求助
可能的核心原因
1. 查询语句(Query)不完全一致
Apollo Client 的 readQuery 要求传入的查询语句必须和写入缓存时的完全匹配——包括字段、嵌套结构、指令(如@client)甚至字段顺序。哪怕只是少了一个嵌套字段,或者多了一个无关指令,readQuery 都会因为无法匹配到缓存中的查询结果而返回 undefined,哪怕缓存里确实存在对应的数据。
2. 变量类型/值不匹配
缓存中存储数据的 key 是由查询语句和变量共同生成的。如果 saga1 中 query 使用的变量和 saga2 中 readQuery 的变量存在类型差异(比如一个传数字123,一个传字符串'123'),哪怕值看起来一样,也会生成不同的缓存 key,导致 readQuery 找不到对应数据。
3. 异步执行顺序的细微偏差
虽然路由变化时先触发 saga1,但 client.query 是异步操作(返回 Promise)。如果 saga1 没有等待这个 Promise 完成就触发了 saga2,可能出现:readQuery 执行时缓存写入还在微任务队列中,而后续的 cache.extract() 是在微任务完成后执行,所以能看到数据。
4. 缓存归一化配置问题
如果使用了自定义的 dataIdFromObject 配置,若该函数生成的 ID 不符合预期,会导致缓存中的对象无法被 readQuery 正确关联。比如 dataIdFromObject 没有正确提取 objectId,或者生成的 ID 格式和查询期望的不一致。
对应解决方案
1. 严格对齐查询语句
直接复用同一个 AQuery 常量,确保 saga1 和 saga2 使用的查询完全一致。可以把查询语句单独抽成一个文件导出,避免手动复制导致的差异:
// 单独导出查询 export const AQuery = gql` query AQuery($objectId: String!) { // 确保字段、嵌套完全一致 item(objectId: $objectId) { id name // 所有需要的字段 } } `;
2. 校验变量的类型和值
在调用 readQuery 前,打印对比 saga1 和 saga2 的变量,确保类型和值完全相同。比如:
// 在saga1中打印变量 console.log('query variables:', { objectId:'123' }, typeof { objectId:'123' }.objectId); // 在saga2中打印变量 console.log('readQuery variables:', { objectId:'123' }, typeof { objectId:'123' }.objectId);
如果发现类型不一致,统一变量类型(比如都转成字符串或数字)。
3. 确保异步操作完成后再读取缓存
在 saga1 中使用 yield call 等待 client.query 完成,再触发后续逻辑;或者使用 saga 的 effect 控制执行顺序,比如用 take 等待 saga1 完成的 action 后再执行 saga2:
// Saga1 修正:等待query完成 const newData = yield call(() => getApolloClient().query({ query: AQuery, fetchPolicy: 'network-only', variables: { objectId:'123' }, })); yield put({ type: 'QUERY_COMPLETED', data: newData }); // Saga2 修正:等待QUERY_COMPLETED action后再读取 yield take('QUERY_COMPLETED'); const result = getApolloClient().readQuery({ query: AQuery, variables: { objectId:'123' }, });
4. 检查缓存归一化配置
如果自定义了 dataIdFromObject,确保它能正确生成唯一 ID。比如:
const cache = new InMemoryCache({ dataIdFromObject: (object) => { switch (object.__typename) { case 'Item': return `Item:${object.objectId}`; // 确保和查询变量匹配 default: return defaultDataIdFromObject(object); } }, });
可以用 cache.identify(item) 打印缓存中对象的 ID,对比 readQuery 期望的 ID 是否一致。
5. 临时用 readFragment 替代 readQuery(排查用)
如果怀疑是查询字段不匹配,可以用 readFragment 直接读取缓存中的对象,绕过查询语句的匹配要求:
const result = getApolloClient().cache.readFragment({ id: 'Item:123', // 替换成实际的缓存ID fragment: gql` fragment ItemData on Item { id name // 需要的字段 } `, });
如果能读取到数据,说明问题确实出在 readQuery 的语句匹配上。
内容的提问来源于stack exchange,提问作者reactdesign

