为何@graphql-codegen/typescript将解析器父类型声明为输出类型?
我正在使用@graphql-codegen/typescript为以下GraphQL Schema生成类型:
type Book { title: String author: String comment: String } type Query { books: [Book] }
生成的Book类型代码如下:
export type Book = { __typename?: 'Book'; author?: Maybe<Scalars['String']['output']>; comment?: Maybe<Scalars['String']['output']>; title?: Maybe<Scalars['String']['output']>; };
这个TS类型Book被用作GraphQL类型Book字段解析器的父输入类型。我理解GraphQL类型Book的字段未标记非空,查询返回时字段可以为null或被省略,但这和解析器的输入类型无关。我不明白为什么生成的Book字段既是可选类型,又被包裹在Maybe中——毕竟父解析器返回的对象里如果省略某个字段,并不会触发该字段的解析器;而字段返回null或报错是解析器输出端的问题,和输入没有直接关联。
我实现的解析器代码如下:
const books = [ { title: 'The Awakening', author: 'Kate Chopin', }, { title: 'City of Glass', author: 'Paul Auster', }, ]; const resolvers: Resolvers = { Query: { books: () => books, }, Book: { title: (book: Book) => { console.log("title", book); return book.title!; }, comment: (book: Book) => { console.log("comment", book); return "foobar"; } }, };
父解析器返回的对象没有comment字段,但包含title和author,comment字段由解析器动态生成,运行在Apollo Server上。实际观察到,传给字段解析器的父对象就是父解析器返回的内容,哪怕查询没有请求某些字段。但代码生成器把解析器输入类型生成得和输出类型结构一致(字段可选/带Maybe),我对此感到困惑,想了解其中原因。
补充生成的基础类型定义:
export type Maybe<T> = T | null; export type Scalars = { ID: { input: string; output: string; } String: { input: string; output: string; } Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } };
原因解析
GraphQL Codegen生成这种类型是出于以下几个核心设计考量:
类型复用简化维护
Codegen默认会复用同一类型来表示GraphQL类型的输出结构和解析器的父输入结构,以此减少重复的类型定义,降低代码维护成本。虽然输出和输入的场景逻辑不同,但从TypeScript类型系统的角度,复用能避免冗余,简化整体类型体系。严格遵循Schema契约
GraphQL Schema中字段未标记非空(!),意味着该字段在输出层面允许为null或缺失。Codegen生成Maybe<T>+可选字段的类型,是为了严格贴合Schema的约束——即使在解析器输入场景下,它也假设父对象可能符合Schema定义的所有合法输出情况(比如父解析器可能返回带null字段的对象,或某些字段缺失),这是一种防御性的类型设计,确保类型能覆盖所有潜在的合法数据结构。适配数据源的不确定性
实际业务中,父解析器的数据源可能多样(比如数据库返回的字段可能为null,或者不同环境下返回的字段结构不一致),生成可选+Maybe的类型可以适配这些不确定情况,避免出现TypeScript编译错误。
优化方案
如果希望解析器的父类型更贴合实际的父对象结构,可以使用typescript-resolvers插件,它会生成更精准的解析器类型,明确区分输出类型和父输入类型。
在codegen.yml中添加配置:
generates: ./src/types.ts: plugins: - "typescript" - "typescript-resolvers" config: resolvers: true
配置后,生成的解析器父类型会基于你实际返回的数据源结构,比如针对上述场景,父类型会要求title和author字段存在,而comment为可选(因为父对象中没有该字段),更符合实际的解析器输入逻辑。
内容的提问来源于stack exchange,提问作者Martin Geisse

