资讯动态

Apollo Client 本地状态 Codegen 插件实战指南:为 LocalState 自动生成类型安全的 Resolver 类型

发布时间:2026/9/20 20:44:24 来源:尧图企业网站定制
前端GraphQL【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址https://gitcode.com/gh_mirrors/ap/apollo-client点击查看免费下载导读Apollo Client 的本地状态client字段依赖LocalState在客户端执行 resolver 函数而手写这些 resolver 的类型签名既繁琐又容易与服务端 schema 类型脱节。本文围绕仓库中apollo/client-graphql-codegen包codegen/的演进记录与源码实现系统讲解其提供的local-stateCodegen 插件如何把它接入 GraphQL Codegen 配置、如何解读生成的Resolvers类型并把它交给LocalState做类型检查以及baseTypesPath、contextType、nonOptionalTypename等核心配置项的语义与底层原理。读完本文你将能在自己的项目中为本地 resolver 一键生成与LocalState严格匹配的 TypeScript 类型并理解生成逻辑背后的取舍。一、这个插件解决什么问题在 Apollo Client 中client字段由客户端本地的 resolver 函数提供值而不是发往服务端。LocalState是承载这套机制的类见 src/local-state/LocalState.ts它接收一个resolvers映射形如const resolvers { Query: { isLoggedIn: () !!localStorage.getItem(token) }, Mutation: { login: (_, { token }) { /* ... */ return true; } }, };为了让这些 resolver 获得完整的类型提示参数、父对象、上下文、返回值传统做法是使用graphql-codegen/typescript-resolvers生成类型。但该插件面向服务端执行环境签名与LocalState的 resolver 契约并不完全一致。根据 codegen/CHANGELOG.md 中 1.0.0 版本的说明Apollo 团队在 PR #12617 中引入了apollo/client-graphql-codegen包一个专门为LocalState定制的 GraphQL Codegen 插件。它与graphql-codegen/typescript-resolvers思路类似但生成的是能直接用于LocalState的 resolver 类型从而把本地字段的类型检查完全自动化。二、安装与最小配置该插件以独立包发布名称为apollo/client-graphql-codegencodegen/package.json当前版本为 2.1.1插件入口通过包的exports字段暴露为./local-state。在项目里安装后将它加入 codegen 配置即可// codegen.ts import type { CodegenConfig } from graphql-codegen/cli; const config: CodegenConfig { // ... generates: { ./path/to/local/resolvers.ts: { schema: [./path/to/localSchema.graphql], plugins: [typescript, apollo/client-graphql-codegen/local-state], // ... }, }, }; export default config;关键点schema应指向你的本地 schema 文件即只包含client本地字段的那份.graphql定义而不是整个应用的完整 schema。CHANGELOG 中特别强调如果传入完整应用 schema插件会为远程字段也生成 resolver 类型产生大量无意义的代码。typescript插件必须先于local-state插件因为它生成的Scalars、Maybe、RequireFields等基础类型是后续输出所依赖的在 integration-tests/codegen/codegen.ts 的真实配置中两者总是成对出现。运行graphql-codegen后生成的文件里会包含一个Resolvers类型把它作为泛型参数传给LocalState即可让LocalState对 resolver 的返回值和入参做完整检查import type { Resolvers } from ./path/to/resolvers-types.ts; const localState new LocalStateResolvers({ // resolvers 现在会按类型约束校验 });LocalState的泛型签名定义在 src/local-state/LocalState.tsTResolvers被约束为LocalState.Resolvers的子类型当传入生成的Resolvers后context类型也会通过InferContextValueFromResolvers从 resolver 签名中被自动推导出来。三、推荐配置项详解CHANGELOG 还给出了一组官方推荐配置全部可以用satisfies LocalStatePluginConfig获得静态校验// codegen.ts import type { LocalStatePluginConfig } from apollo/client-graphql-codegen/local-state; const config: CodegenConfig { // ... generates: { ./path/to/local/resolvers.ts: { config: { // 确保所有返回对象或数组类型的 client 字段都带上 __typename nonOptionalTypename: true, // 当本地 schema 通过 extend type 扩展了已有类型时此项必填 baseTypesPath: ./relative/path/to/base/schema/types, // 如果你用 context 函数自定义了上下文值在此指定其路径与类型 contextType: ./path/to/contextValue#ContextValue, } satisfies LocalStatePluginConfig, }, }, };三个核心配置的作用与底层依据如下3.1nonOptionalTypename当本地 resolver 返回对象或数组时LocalState在解析子字段前要求结果必须携带__typename否则会在运行时抛出 Could not resolve __typename 错误见 src/local-state/LocalState.ts。开启该选项后生成的类型会把__typename字段声明为非可选从类型层面强制 resolver 返回它。3.2baseTypesPath当本地 schema 中存在extend type User { ... }这类扩展定义时插件需要知道基础类型远程 schema 类型的存放位置。在 codegen/local-state/plugin.ts 中插件会扫描 schema 的类型映射通过type.astNode?.loc?.startToken.value extend识别被扩展的类型一旦检测到扩展类型而baseTypesPath未配置会直接抛错baseTypesPath must be defined when your local schema extends existing schema types.配置后插件会生成类似import * as BaseSchemaTypes from baseTypesPath的命名空间导入并用它来组合父类型。3.3contextTypeLocalState支持通过context函数为每个 resolver 提供自定义上下文LocalState.ContextFunction见 src/local-state/LocalState.ts。若你定义了上下文类型用module#Type语法把它传给插件未配置时默认使用apollo/client导出的DefaultContext该默认值定义在 codegen/local-state/visitor.ts。生成的每个LocalState.Resolver泛型的第三个参数即该上下文类型。四、从源码看插件的完整配置面插件公开的配置类型LocalStatePluginConfig定义在 codegen/local-state/config.ts在三个核心配置之外还继承了graphql-codegen/visitor-plugin-common的RawConfig并扩展了以下常用项配置项类型默认值作用baseSchemaTypesImportNamestringBaseSchemaTypes基础 schema 类型命名空间的导入名avoidOptionalsboolean \| AvoidOptionalsConfigfalse为 true 时去掉?强制实现所有字段 resolver否则会编译失败addUnderscoreToArgsTypebooleanfalse给生成的Args类型加_前缀避免标识符冲突mapperTypeSuffixstring—给 mapper 导入名加后缀防止命名冲突mappers{ [typeName]: string }—用自定义类型module#type语法替换 GraphQL 类型的默认映射defaultMapperstring由typescript插件生成的类型未被mappers覆盖时的兜底映射支持Partial{T}等占位符写法showUnusedMappersbooleantrue是否在未使用某个 mapper 时打印警告immutableTypesbooleanfalse生成readonly属性与ReadonlyArraynamespacedImportNamestring给生成的类型加命名空间前缀便于拆分文件resolverTypeSuffixstringResolvers每个类型 resolver 的命名后缀allResolversTypeNamestringResolvers汇总全部 resolver 签名的统一导出类型名其中showUnusedMappers的警告输出console.warn(Unused mappers: ...)实现在 codegen/local-state/plugin.ts默认开启可在配置中关闭。另外visitor.ts中的ScalarTypeDefinition会针对自定义标量发出警告Custom scalars type X is ignored and cannot be resolved with LocalState. Please map the scalar type to a primitive with the scalars config.—— 即本地 schema 里的自定义标量需要通过scalars配置映射为 TypeScript 原始类型。五、生成结果长什么样仓库自带的 fixtures 是理解生成产物的最佳样例。输入是本地 schema src/local-state/tests/LocalState/fixtures/localSchema.graphqlextend type Query { currentUserId: ID } extend type User { isLoggedIn: Boolean! favoriteFood: Food } type Food { name: String categories(limit: Int, offset: Int!): [FoodCategory!] } enum FoodCategory { ITALIAN }经过typescriptlocal-state两个插件配置见 integration-tests/codegen/codegen.ts生成的 local-resolvers.ts 展示了三个重要特征1. 扩展类型的父类型被DeepPartialOmit处理。由于本地 resolver 收到的父对象只包含服务端 schema 的字段插件生成了OmitDeepPartialBaseSchemaTypes.User, isLoggedIn | favoriteFood作为User的父类型把本地字段从父对象上剔除避免错误地把本地字段当作父对象已有属性对应实现见 codegen/local-state/visitor.ts。2. 每个字段的签名统一为LocalState.Resolver...泛型。例如export type UserResolvers { isLoggedIn?: LocalState.Resolver ResolversTypes[Boolean], // 返回值 ResolversParentTypes[User], // 父对象 ContextValue, // 上下文 // 有必填参数时Args 类型会被 RequireFields 包裹 ; };这与LocalState.ResolverTResult, TParent, TContext, TArgs的四个泛型参数一一对应见 src/local-state/LocalState.ts保证生成的类型与运行时解析逻辑严格对齐。3. 顶层Resolvers汇总类型。文件末尾导出export type Resolvers { Food?: FoodResolvers; Query?: QueryResolvers; User?: UserResolvers; };这正是传给new LocalStateResolvers({ ... })的那个类型。六、插件的执行流程源码级原理local-state插件的核心实现是 codegen/local-state/plugin.ts 中的plugin函数整体流程如下识别扩展类型遍历schema.getTypeMap()找出以extend关键字定义的类型若存在且未配置baseTypesPath则直接报错。运行 Visitor通过LocalStateVisitorcodegen/local-state/visitor.ts遍历 schema AST。Visitor 继承自BaseResolversVisitor但做了大量面向LocalState的定制字段类型固定为LocalState.Resolver而不是服务端常见的ResolverFn列表与可空类型统一用MaybeT包裹ListType、NamedType均返回Maybe...NonNullType则清除Maybe对扩展类型生成OmitDeepPartialBaseType, 本地字段的父类型对自定义标量发出忽略警告。组装输出预置import type { LocalState } from apollo/client/local-state与import type { DeepPartial } from apollo/client/utilities见 codegen/local-state/plugin.ts再拼接ResolversTypes、ResolversParentTypes、各类型Resolvers与顶层Resolvers声明。LocalState.Resolver的四参数签名rootValue、args、context、info在运行时由resolveClientField按相同顺序调用src/local-state/LocalState.ts生成类型与执行逻辑在契约上是闭环的。七、版本演进与依赖要求从 codegen/CHANGELOG.md 可以清晰看到这个插件的演进轨迹版本变更类型内容1.0.0-alpha.0 / 1.0.0Major引入local-state插件PR #12617首个面向LocalState的 resolver 类型生成器1.0.0-rc.0Major仅为以rc版本发布而做的版本号推进PR #127232.0.0Major将上游依赖整体升级到大版本PR #130142.1.0Minor支持 peer dependencies 中最新一代 GraphQL Codegen 包的大版本PR #133012.1.1Patch修复上一个 minor 版本遗漏的运行时文件PR #13308其中 2.1.0 提到的 latest GraphQL Codegen package major versions 可以从 codegen/package.json 的peerDependencies得到印证当前支持的双版本范围是graphql-codegen/plugin-helpers^6.0.0 || ^7.0.0graphql-codegen/typescript^5.0.0 || ^6.0.0graphql-codegen/visitor-plugin-common^6.0.0 || ^7.0.0因此在接入或升级该插件时请确保项目中上述三个 Codegen 包满足对应的版本区间否则需要同时升级这正是 2.0.0 与 2.1.0 两个版本出现的原因。八、最佳实践与注意事项综合 CHANGELOG、源码与仓库内置 fixtures接入该插件时建议遵循以下原则schema 只放本地字段schema选项始终指向本地 schemaextend type Query、extend type User等不要混入远程 schema避免为远程字段生成无用的 resolver 类型。typescript插件放在前面生成结果依赖其基础类型Maybe、Scalars、RequireFields等。本地 schema 扩展远程类型时必配baseTypesPath否则插件会在生成阶段直接报错fixtures 中 base-types.ts 展示了基础类型文件应如何组织一般由主 codegen 流程对远程 schema 生成。自定义上下文务必配置contextType不配置时默认是DefaultContext若运行时实际提供了自定义 context类型将与行为不符。返回对象/数组的本地字段开启nonOptionalTypename与LocalState运行时的__typename强校验配合把运行时错误提前到编译期。自定义标量需要通过scalars配置映射否则生成阶段会忽略并打印警告字段类型退化为不精确的any。结语apollo/client-graphql-codegen的local-state插件把 Apollo Client 本地状态的类型安全问题从手写易错变成了生成即正确它复用 GraphQL Codegen 的成熟架构却把签名契约完全对齐到LocalState.Resolver。理解它的配置项与生成策略不仅能让你的client字段获得完整的类型推导也能在升级 Codegen 生态时从容应对 peer dependency 的版本变化。赞分享前端GraphQL【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址https://gitcode.com/gh_mirrors/ap/apollo-client点击查看免费下载相关推荐Apollo Client LocalState 深度指南用 client 指令与本地 Resolver 构建客户端状态管理Apollo Client LocalState 深度指南用 client 指令与本地 Resolver 构建客户端状态管理 Apollo Client 的前端GraphQLSpacedrive TypeScript 客户端实战基于 Specta 自动生成类型的全栈类型安全调用指南Spacedrive TypeScript 客户端实战基于 Specta 自动生成类型的全栈类型安全调用指南 Spacedrive 的核心是一个用 Rust桌面应用移动开发后端存储数据同步终极防撤回指南如何让微信QQ消息不再消失的完整教程终极防撤回指南如何让微信QQ消息不再消失的完整教程 在数字沟通时代你是否曾因对方撤回了一条重要消息而感到困扰无论是商务谈判中的关键条款、客户沟通中的重桌面应用即时通讯创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价