☰
TypeGraphQL 继承机制全解析:类型继承与 Resolver 类继承的实战指南
2026/9/28 20:27:36 网站建设 项目流程
  • 后端
  • GraphQL
  • API设计

【免费下载链接】type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!

项目地址:https://gitcode.com/gh_mirrors/ty/type-graphql
点击查看免费下载

导读

本文基于 TypeGraphQL v0.17.0 官方文档《Inheritance》,系统讲解 TypeGraphQL 中两大继承能力——类型继承(Types Inheritance)与Resolver 类继承(Resolvers Inheritance)。读完本文,你将掌握:通过继承复用@ArgsType()/@InputType()/@ObjectType()类型定义以贯彻 DRY 原则;通过工厂函数配合@Resolver({ isAbstract: true })搭建可复用的基础 CRUD Resolver,并用name选项生成唯一查询/变更名;同时理解父类方法覆盖的规则与常见tsconfig编译陷阱。

背景:TypeGraphQL 的类与装饰器建模

TypeGraphQL 的核心思想是用 TypeScript 类 + 装饰器来定义 GraphQL Schema——@ObjectType()描述输出对象、@InputType()描述输入对象、@ArgsType()描述参数集合,@Resolver()描述解析器。既然类的组合与复用是面向对象编程的基本功,TypeGraphQL 自然允许通过**类的继承(extends)**来组合类型定义与解析器行为,从而避免重复代码(DRY,Don't Repeat Yourself)。

Types Inheritance:类型继承

场景:复用分页参数

在 GraphQL API 中,skip与take这类分页参数几乎是所有列表查询的标配。与其在每个 Resolver 参数类中重复声明,不如先声明一次:

@ArgsType() class PaginationArgs { @Field(type => Int) skip: number = 0; @Field(type => Int) take: number = 25; }

然后在任意参数类中通过extends复用:

@ArgsType() class GetTodosArgs extends PaginationArgs { @Field() onlyCompleted: boolean = false; }

这样GetTodosArgs既拥有skip、take,又新增了onlyCompleted,Schema 中会完整展开父类字段。

适用类型与对象/输入类型

同样的技术同样适用于input type 类(@InputType())和object type 类(@ObjectType()):

@ObjectType() class Person { @Field() age: number; } @ObjectType() class Student extends Person { @Field() universityName: string; }

关键约束:父子必须使用同一种类型装饰器

父类与子类必须使用同类型的装饰器——上面的Person -> Student都使用了@ObjectType()。混用是不允许的,例如子类用@ObjectType()、父类用@InputType(),会导致 Schema 构建错误。这是因为 TypeGraphQL 在生成 Schema 时依赖元数据(装饰器收集的字段信息、类类型标记)来归属字段与校验一致性,装饰器类型不一致意味着同一组字段被登记到了不同的类型系统中,Schema 生成器无法处理这种冲突。从源码角度看,类元数据与字段元数据存储在同一个 MetadataStorage 中(参见 metadata-storage.ts),Schema 生成阶段(schema-generator.ts)会按类类型分别生成 Input/Object 定义,混用装饰器必然破坏这一对应关系。

与接口继承的组合

除了类的继承,TypeGraphQL 还支持接口(@InterfaceType())与类继承的组合使用。仓库中的 interfaces-inheritance 示例 展示了这种更进阶的用法:Person是接口,Student与Employee是分别继承该接口的@ObjectType()类;MultiResolver(resolver.ts)中的persons()查询返回IPerson[],由 GraphQL 根据实际对象类型动态解析。注意,当某个实现了接口的类型未直接在 Schema 中出现时,需要在buildSchema中通过orphanedTypes显式注册,见 index.ts。

Resolvers Inheritance:Resolver 类继承

类型继承解决的是“字段复用”,而Resolver 类继承解决的是“行为复用”——例如为每个实体/资源创建一个基础 CRUD Resolver,从而省去重复的样板代码。

为什么需要工厂函数

TypeGraphQL 要求 Schema 中的查询/变更名称全局唯一。如果直接定义一个具名的基础 Resolver 类,子类继承后所有实体都会生成同名的查询,导致冲突。因此需要工厂函数:在工厂内部动态创建基础类,并利用参数生成唯一名称。

第一步:定义抽象基础类工厂

function createBaseResolver() { abstract class BaseResolver {} return BaseResolver; }

tsconfig 编译陷阱:当tsconfig.json中启用了declarations: true(生成.d.ts声明文件)时,可能报错[ts] Return type of exported function has or is using private name 'BaseResolver'。解决方案有两种:将返回类型声明为any;或者单独定义一个类/接口来描述该类的方法与属性,供导出函数签名引用。

第二步:让工厂接收类型与名称后缀参数

为了让工厂生成唯一的查询/变更名,并让 Schema 知道返回类型,需要传入suffix(名称后缀)与对象类型类:

function createBaseResolver<T extends ClassType>(suffix: string, objectTypeCls: T) { abstract class BaseResolver {} return BaseResolver; }

这里用到了 TypeGraphQL 提供的ClassType工具类型(见 ClassType.ts),它描述了一个“类构造器”的类型约束。

第三步:标记@Resolver({ isAbstract: true })

非常重要:必须用@Resolver装饰器标记BaseResolver,并传入isAbstract: true选项。否则多个继承该基础类的子 Resolver 会因注册同名查询/变更而抛出错误:

function createBaseResolver<T extends ClassType>(suffix: string, objectTypeCls: T) { @Resolver({ isAbstract: true }) abstract class BaseResolver {} return BaseResolver; }

isAbstract的作用是告诉 Schema 生成器:这个 Resolver 类本身不直接产生 Schema 字段,只是作为其他 Resolver 的父类模板,从而避免重复注册。从源码看,Resolver 元数据(resolver-metadata.ts)记录了isAbstract标记,Schema 生成器会据此跳过抽象类的字段生成。

第四步:实现可复用的查询/变更方法

在基础类中实现方法的方式与普通 Resolver 完全一致,唯一区别是可以通过name装饰器选项覆盖最终写入 Schema 的名称:

function createBaseResolver<T extends ClassType>(suffix: string, objectTypeCls: T) { @Resolver({ isAbstract: true }) abstract class BaseResolver { protected items: T[] = []; @Query(type => [objectTypeCls], { name: `getAll${suffix}` }) async getAll(@Arg("first", type => Int) first: number): Promise<T[]> { return this.items.slice(0, first); } } return BaseResolver; }

这里getAll${suffix}为每个资源生成形如getAllPerson、getAllRecipe的查询名。name选项同样适用于@Mutation与@Subscription。

第五步:创建具体 Resolver 并继承

const PersonBaseResolver = createBaseResolver("person", Person); @Resolver(of => Person) export class PersonResolver extends PersonBaseResolver { // ... }

子类中还能继续添加专属的查询与变更,像普通 Resolver 一样使用:

const PersonBaseResolver = createBaseResolver("person", Person); @Resolver(of => Person) export class PersonResolver extends PersonBaseResolver { @Mutation() addPerson(@Arg("input") personInput: PersonInput): Person { this.items.push(personInput); return personInput; } }

最后只需在buildSchema中正常注册PersonResolver即可,继承的基础 Resolver 会自动生效:

const schema = await buildSchema({ resolvers: [PersonResolver] });

覆盖父类方法的规则

若想在子类中覆盖父类声明的查询/变更/订阅,必须让 Schema 名称保持一致(通过name选项或类方法名)。覆盖会同时替换掉 GraphQL 参数与返回类型。注意:仅仅在子类中提供同名但不同 Schema 名称的方法(例如新增一个getOne方法)不会覆盖父类实现——因为 Schema 中会同时存在两个不同名字的字段,父类字段仍然保留。

仓库中的完整进阶示例

resolvers-inheritance 示例 展示了生产级写法。其基础工厂 resource.resolver.ts 相比文档示例更进一步:

  • 通过ResourceCls.name.toLocaleLowerCase()动态生成资源名(如person/recipe),再拼接出person、persons、recipe、recipes等查询名;
  • 用@FieldResolver({ name: "uuid" })为所有子资源动态附加uuid字段;
  • 结合typedi容器(@Service()与构造器注入ResourceServiceFactory)实现依赖注入。

子类只需一行继承即可获得完整 CRUD 能力:

@Resolver() @Service() export class PersonResolver extends ResourceResolver(Person, persons) { @Mutation() promote(@Arg("personId", _type => Int) personId: number): boolean { const person = this.resourceService.getOne(personId); // ... } }

见 person.resolver.ts。生成的 Schema 中查询persons(skip: Int! = 0, take: Int! = 10)与person(id: Int!)均来自继承的基础类,可对照 schema.graphql 验证。

版本差异提醒

本仓库中的 docs/inheritance.md 与 version-0.17.0 文档 略有差异:新版文档的基础类示例使用了@Resolver()(不带isAbstract)。在 v0.17.0 版本上,务必加上isAbstract: true,否则多个子 Resolver 会因同名查询/变更注册而报错;这是 0.17.0 文档中的关键提醒。

小结

继承类型装饰器要求用途核心要点
Types Inheritance父子同类装饰器(同为@ObjectType()/@InputType()/@ArgsType())复用字段定义(如分页参数)混用装饰器会引发 Schema 构建错误
Resolvers Inheritance父类@Resolver({ isAbstract: true })复用查询/变更/订阅实现(如基础 CRUD)工厂函数 +name选项生成唯一名称;覆盖需同名同参

两种继承机制都贯彻了 DRY 原则:类型继承消除字段冗余,Resolver 继承消除逻辑冗余。理解它们之后,你可以用极少量的代码为多个实体搭建风格统一的完整 GraphQL API,这正是 TypeGraphQL 面向对象建模能力的核心价值所在。

  • 后端
  • GraphQL
  • API设计

【免费下载链接】type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!

项目地址:https://gitcode.com/gh_mirrors/ty/type-graphql
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询