如何在Apollo Client的InMemoryCache中为匹配正则的数据设置最大缓存时间
核心疑问解答
你提到的自定义Token类型方案中,要触发缓存过期重新拉取,只需要在read函数中返回undefined即可:Apollo Client会判定字段缓存缺失,自动触发服务端请求获取最新值。
可选实现方案
方案1:自定义Token类型实现(最稳定,推荐可控场景使用)
通过read+merge字段策略组合可以实现完整的过期控制,完整代码示例如下:
// 全局存储token的写入时间,WeakMap支持自动垃圾回收不会额外占用内存 const tokenWriteTime = new WeakMap(); // 自定义过期时长,示例为5分钟 const TOKEN_EXPIRE_MS = 5 * 60 * 1000; const cache = new InMemoryCache({ typePolicies: { Token: { fields: { id: { // 写入缓存时触发,记录写入时间 merge(existing, incoming) { tokenWriteTime.set(incoming, Date.now()); return incoming; }, // 读取缓存时触发,判断是否过期 read(cachedVal) { if (!cachedVal) return undefined; const writeTime = tokenWriteTime.get(cachedVal); // 过期则返回undefined触发重拉,否则返回缓存值 if (!writeTime || Date.now() - writeTime > TOKEN_EXPIRE_MS) { return undefined; } return cachedVal; } } } } } });
方案2:全局字段读取钩子(适配未知字段名、正则匹配值的场景)
InMemoryCache支持全局默认类型策略,所有类型的所有字段读取都会触发该钩子,你可以在钩子中匹配值的正则实现过期控制:
const fieldWriteTime = new WeakMap(); const TOKEN_VALUE_REG = /^short-lived-token-.+/; const TOKEN_EXPIRE_MS = 5 * 60 * 1000; const cache = new InMemoryCache({ typePolicies: { // 匹配所有GraphQL类型 __default: { fields: { // 匹配类型下的所有字段 __default: { merge(existing, incoming) { if (typeof incoming === 'string' && TOKEN_VALUE_REG.test(incoming)) { fieldWriteTime.set(incoming, Date.now()); } return incoming; }, read(cachedVal) { if (typeof cachedVal !== 'string' || !TOKEN_VALUE_REG.test(cachedVal)) { return cachedVal; } const writeTime = fieldWriteTime.get(cachedVal); if (!writeTime || Date.now() - writeTime > TOKEN_EXPIRE_MS) { return undefined; } return cachedVal; } } } } } });
注意:该方案会对所有字段读取做正则校验,性能损耗极低,如果你的业务中token正则没有匹配冲突可以直接使用。
方案3:基于服务端@token指令实现(最精准,无正则误判风险)
配合Apollo Client的指令处理逻辑,在响应返回时标记所有带@token指令的字段,后续读取时做过期判断即可:
// 存储所有带@token指令的字段标识(格式为「类型名.字段名」避免同名字段冲突) const tokenFields = new Set(); const fieldWriteTime = new Map(); const TOKEN_EXPIRE_MS = 5 * 60 * 1000; // 新增Apollo Link层处理查询,收集token字段 const tokenDirectiveLink = new ApolloLink((operation, forward) => { // 遍历查询AST,收集所有带@token指令的字段 operation.query.definitions.forEach(def => { def.selectionSet?.selections?.forEach(sel => { if (sel.directives?.some(d => d.name.value === 'token')) { const typeName = operation.query.definitions[0].operation === 'query' ? 'Query' : 'Mutation'; tokenFields.add(`${typeName}.${sel.name.value}`); } }); }); return forward(operation); }); // 缓存配置全局钩子 const cache = new InMemoryCache({ typePolicies: { __default: { fields: { __default: { merge(existing, incoming, { typename, fieldName }) { const fieldKey = `${typename}.${fieldName}`; if (tokenFields.has(fieldKey)) { fieldWriteTime.set(fieldKey, Date.now()); } return incoming; }, read(cachedVal, { typename, fieldName }) { const fieldKey = `${typename}.${fieldName}`; if (!tokenFields.has(fieldKey)) return cachedVal; const writeTime = fieldWriteTime.get(fieldKey); if (!writeTime || Date.now() - writeTime > TOKEN_EXPIRE_MS) { return undefined; } return cachedVal; } } } } } }); // 初始化Apollo Client时注入链路 const client = new ApolloClient({ link: tokenDirectiveLink.concat(createHttpLink({ uri: '/graphql' })), cache });
注意事项
- 如果多个不同字段返回相同的token值,WeakMap的存储逻辑可以正常工作,不会互相影响
- 若需要避免过期瞬间多个相同请求并发,可以加简单的请求防抖逻辑,同一字段同一时间只发起一次重拉请求
- 返回
undefined触发的重拉会自动合并到原查询中,不会产生额外的无效查询
内容的提问来源于stack exchange,提问作者light24bulbs
相关产品推荐
相关产品推荐

