如何在Apollo Client policies中使用readField对带参数的查询做子选择
Apollo Client 3.4.7 带参数查询的缓存子集读取最优方案
核心实现思路
Apollo 官方的 readField 方法原生支持传入变量参数,会自动按照缓存规则拼接带参数的缓存键,不需要手动拼接键名,也不需要直接调用 cache.readQuery 处理引用转换。
具体实现步骤
- 先给自定义的
readManagers字段声明和原查询一致的入参,保证参数可以透传到读取逻辑 - 在
read函数中调用readField时传入对应变量,Apollo 会自动匹配对应参数的缓存数据
完整代码示例
import { TypePoliciesSchema, gql, readField } from '@apollo/client'; const schema: TypePoliciesSchema = { typeDefs: gql` extend type Query { // 声明和原employees查询一致的入参 readManagers(companyId: ID!): [Employee!]! } `, typePolicies: { Query: { fields: { readManagers: { read(_, context) { // 直接传入变量,readField自动匹配对应缓存 const employees = readField<Query, 'readEmployees'>( context, 'readEmployees', undefined, // 第四个参数传入变量,Apollo自动生成对应缓存键 { companyId: context.args.companyId } ); return employees?.filter(employeeRef => { const employeeRole = readField<Employee, 'role'>(context, 'role', employeeRef); return employeeRole === EmployeeRole.Manager; }) ?? []; }, } } } } }
如果用 context.readField 写法,代码更简洁:
read(_, context) { const employees = context.readField( 'readEmployees', undefined, { companyId: context.args.companyId } ); // 后续过滤逻辑不变 }
进一步减少样板代码的封装方案
如果有大量类似的子集读取需求,可以封装通用的字段生成函数,避免重复写逻辑:
import { FieldFunctionOptions } from '@apollo/client'; type FilterFieldConfig<T = unknown> = { // 原查询字段名 sourceField: string; // 过滤规则 filter: (item: T, context: FieldFunctionOptions) => boolean; } function createFilteredField<T = unknown>(config: FilterFieldConfig<T>) { return { read(_: unknown, context: FieldFunctionOptions) { const sourceList = context.readField( config.sourceField, undefined, context.args ) as T[]; return sourceList?.filter(item => config.filter(item, context)) ?? []; } } }
封装后使用方式如下:
const schema: TypePoliciesSchema = { typeDefs: gql` extend type Query { readManagers(companyId: ID!): [Employee!]! readDesigners(companyId: ID!): [Employee!]! } `, typePolicies: { Query: { fields: { readManagers: createFilteredField<Employee>({ sourceField: 'readEmployees', filter: (emp, ctx) => ctx.readField('role', emp) === EmployeeRole.Manager }), readDesigners: createFilteredField<Employee>({ sourceField: 'readEmployees', filter: (emp, ctx) => ctx.readField('role', emp) === EmployeeRole.Designer }), } } } }
方案优势
- 完全复用 Apollo 原生缓存逻辑,不需要手动处理缓存键拼接,不存在匹配错误问题
- 读取到的都是缓存原生引用,响应式更新和原查询完全一致,没有性能损耗
- 封装后几乎没有样板代码,扩展新的子集查询成本极低
内容的提问来源于stack exchange,提问作者R. Chappell
相关产品推荐
相关产品推荐

