Apollo Client缓存未按预期工作:同一查询不同变量重复请求
解决Apollo Client缓存未命中的问题
嘿,这个问题我之前也碰到过!Apollo Client的缓存逻辑有时候确实容易让人困惑,咱们一步步来拆解原因和解决办法:
为什么缓存没生效?
Apollo Client默认的缓存规则是基于查询名称+变量的组合生成唯一缓存键。也就是说,哪怕两个查询返回的是完全相同的文章数据,只要你一个传id变量、一个传slug变量,Apollo就会把它们当成两个完全独立的查询——自然不会复用之前的缓存结果。
不过这里要区分两个缓存层面:
- 对象缓存:Apollo会把返回的每个实体(比如你的
Post)按__typename + 主键(默认是id字段)存在缓存里,所以同一个文章对象其实已经在缓存里了; - 查询缓存:每个查询(带变量)会生成一个独立的缓存条目,用来记录这个查询返回的结果结构。你的问题就出在第二个查询的查询条目不存在,所以Apollo还是会发起请求,哪怕对象已经在缓存里了。
解决办法
1. 先确保实体主键配置正确
首先要让Apollo能识别出不同查询返回的是同一个Post对象。如果你的Post类型主键是id,那默认就没问题;如果是其他字段(比如_id),需要在InMemoryCache里配置typePolicies:
const cache = new InMemoryCache({ typePolicies: { Post: { keyFields: ['id'], // 替换成你的Post类型主键字段 }, }, });
2. 配置缓存重定向(Cache Redirects)
这是核心解决步骤:让通过slug查询的请求,先去缓存里找匹配的Post对象,再直接返回缓存里的对象引用,而不是发起新请求。
在typePolicies里给Query类型的查询字段配置read函数:
const cache = new InMemoryCache({ typePolicies: { Query: { fields: { // 假设你通过slug查询的字段叫postBySlug postBySlug: { read(_, { args, cache, toReference }) { // 方法1:如果你有获取所有文章的查询,可以先查所有文章找匹配的slug const allPostsResult = cache.readQuery({ query: GET_ALL_POSTS }); if (allPostsResult?.posts) { const matchingPost = allPostsResult.posts.find(post => post.slug === args.slug); if (matchingPost) { // 返回这个Post的缓存引用,Apollo会自动从对象缓存里取数据 return toReference({ __typename: 'Post', id: matchingPost.id }); } } // 方法2:如果没有全量查询,可以遍历缓存里的所有Post对象(适合数据量不大的情况) // const postRefs = cache.prefix('Post').map(ref => cache.readFragment({ // id: ref, // fragment: gql`fragment PostSlug on Post { slug id }` // })); // const matchingPost = postRefs.find(post => post?.slug === args.slug); // if (matchingPost) return toReference({ __typename: 'Post', id: matchingPost.id }); // 如果缓存里没找到,就正常发起网络请求 return undefined; }, }, }, }, Post: { keyFields: ['id'], }, }, });
3. 可选:统一查询接口(如果后端支持)
如果你的GraphQL后端支持接收可选的id或slug参数,可以把两个查询合并成一个,这样查询名称统一,变量不同但Apollo能更好地复用对象缓存:
query GetPost($id: ID, $slug: String) { post(id: $id, slug: $slug) { id slug title content # 其他字段 } }
然后在客户端调用时,根据页面传id或slug即可。这种方式配合上面的缓存重定向,效果会更好。
验证缓存是否生效
你可以打开Apollo Client DevTools(Chrome/Firefox扩展),查看缓存里的Post对象和查询条目,确认第二个查询是否真的从缓存里取数据了。
内容的提问来源于stack exchange,提问作者Damien Monni
相关产品推荐
相关产品推荐

