ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TypeGraphQL 接口类型(Interface)完整指南:用 TypeScript 类定义 GraphQL Interface

TypeGraphQL 接口类型(Interface)完整指南:用 TypeScript 类定义 GraphQL Interface 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载导读本指南以 TypeGraphQL 的InterfaceType()装饰器为核心系统讲解如何在 TypeScript 中定义 GraphQL 接口类型、让对象类型实现接口、接口继承接口以及如何为接口字段编写 resolver。读完本文你将掌握接口类型从定义、实现、注册到运行时类型解析resolveType的完整链路并能正确处理 Relay 风格Node接口在多个 schema 中按需暴露等进阶场景。全文示例均可在仓库 examples/interfaces-inheritance 与 tests/functional/interfaces-and-inheritance.ts 中找到可运行佐证。为什么用抽象类来表示接口TypeGraphQL 的核心思想是基于 TypeScript 类来创建 GraphQL 类型。GraphQL 规范本身提供了 Interface Type接口类型用于描述多个对象类型必须共同遵守的字段契约面向对象编程中也会用接口来约束实现类。因此 TypeGraphQL 原生支持定义 GraphQL 接口。但 TypeScript 的interface是纯编译期构造运行时不产生任何实体无法承载装饰器元数据也就无法在运行时构建 GraphQL schema。解决方案是使用抽象类abstract class模拟接口它同样不能被实例化、可以被其他类实现与interface的唯一差别是它不会在编译期强制校验方法实现或字段初始化——只要开发者自觉把抽象类当作接口对待就可以安全使用。定义接口类型创建 GraphQL 接口与创建对象类型几乎一致定义一个抽象类并用InterfaceType()装饰字段形状仍由Field声明InterfaceType() abstract class IPerson { Field(type ID) id: string; Field() name: string; Field(type Int) age: number; }生成的 GraphQL SDL 为interface IPerson { id: ID! name: String! age: Int! }从源码看InterfaceType装饰器最终调用getMetadataStorage().collectInterfaceMetadata(...)把接口元数据名称、目标类、实现的接口列表、resolveType、autoRegisteringDisabled等存入元数据存储见 src/decorators/InterfaceType.ts 与 src/metadata/definitions/interface-class-metadata.ts。接口类型与其他类型一样由MetadataStorage.build()统一构建元数据src/metadata/metadata-storage.ts 中的buildClassMetadata(this.interfaceTypes)。用对象类型实现接口定义好接口后让对象类型实现它ObjectType({ implements: IPerson }) class Person implements IPerson { id: string; name: string; age: number; }与普通对象类型唯一的区别是必须在ObjectType装饰器中通过implements选项告知 TypeGraphQL 该类实现了哪个接口。实现多个接口时传入数组ObjectType({ implements: [IPerson, IAnimal, IMachine] }) class Hybrid implements IPerson, IAnimal, IMachine { // ... }implements选项在源码中被定义在 src/decorators/types.ts 的ImplementsClassOptions接口中implements?: Function | Function[]ObjectType会将其转为数组存入对象类型元数据src/decorators/ObjectType.ts。当 schema 生成器为对象类型构建interfaces时会逐一在interfaceTypesInfoMap中查找接口类找不到会抛出 Cannot find interface type metadata 错误src/schema/schema-generator.ts 的interfaces回调。字段装饰器可以省略接口中声明的字段会被自动复制到实现该接口的对象类型上下文字段如何合并会从源码层验证因此Person类里无需重复写Field只需依赖 TypeScript 自身的类型检查来保证接口实现正确避免维护两份定义。接口抽象类也可以被继承对象类型可以extends接口抽象类基类的字段会被继承并在 schema 中一并输出ObjectType({ implements: IPerson }) class Person extends IPerson { Field() hasKids: boolean; }schema 生成器在处理对象类型时会通过Object.getPrototypeOf找到父类并复制父类字段src/schema/schema-generator.ts 中 support for extending interface classes - get field info from prototype 的逻辑。字段如何合并到实现类在 schema 生成阶段src/schema/schema-generator.ts 的fields回调中可以看到合并细节先把实现类implements的接口元数据中的字段全部 push 进fieldsMetadata再 push 对象类型自身的字段利用数组顺序让自身字段覆盖继承字段随后统一转换为GraphQLFieldConfigMap。这也解释了为什么接口字段可以在实现类中省略装饰器——它们已被完整搬运。接口实现接口graphql-js 15.0自graphql-js15.0 起接口类型也可以实现其他接口类型。TypeGraphQL 复用了与对象类型相同的implements选项语法InterfaceType() class Node { Field(type ID) id: string; } InterfaceType({ implements: Node }) class Person extends Node { Field() name: string; Field(type Int) age: number; }当对象类型实现了一个已经实现了其他接口的接口时ObjectType的implements数组只需列出继承链上最近的那个接口无需全部列出ObjectType({ implements: [Person] }) class Student extends Person { Field() universityName: string; }上述三段代码在 GraphQL SDL 中会输出interface Node { id: ID! } interface Person implements Node { id: ID! name: String! age: Int! } type Student implements Node Person { id: ID! name: String! age: Int! universityName: String! }注意type Student implements Node PersonNode是被自动补全的。源码中对象类型构建interfaces时会先映射显式列出的接口类再检查对象类型是否继承了父类若父类是接口/对象类型则把父类的interfaces合并进来并去重Array.from(new Set(...))见 src/schema/schema-generator.ts。接口字段的 Resolver 与参数接口上的字段可以定义 resolver语法与对象类型字段完全相同InterfaceType() abstract class IPerson { Field() firstName: string; Field() lastName: string; Field() fullName(): string { return ${this.firstName} ${this.lastName}; } }这些 resolver 会被所有实现该接口的对象类型继承——前提是对象类型自己没有为同名字段提供实现。带参数的接口字段若想声明类似avatar(size: Int!): String!的接口字段直接在方法参数上使用Arg或Args即可InterfaceType() abstract class IPerson { Field() avatar(Arg(size) size: number): string { return http://i.pravatar.cc/${size}; } }生成的接口 SDL 为interface IPerson { avatar(size: Int!): String! }抽象方法的两难TypeScript 不允许在抽象方法上使用装饰器所以如果只想约束方法签名参数与返回类型而不提供实现必须在方法体内抛出错误占位InterfaceType() abstract class IPerson { Field() avatar(Arg(size) size: number): string { throw new Error(Method not implemented!); } }随后所有实现该接口的对象类型都必须继承并覆写此方法ObjectType({ implements: IPerson }) class Person extends IPerson { avatar(size: number): string { return http://i.pravatar.cc/${size}; } }扩展签名如果实现类想为字段追加额外参数如format必须整体重声明完整字段签名无法只追加参数ObjectType({ implements: IPerson }) class Person implements IPerson { Field() avatar(Arg(size) size: number, Arg(format) format: string): string { return http://i.pravatar.cc/${size}.${format}; } }在 Resolver 类中定义接口字段 resolver也可以使用FieldResolver与Root作用目标为接口类Resolver(of IPerson) class IPersonResolver { FieldResolver() avatar(Root() person: IPerson, Arg(size) size: number): string { return http://typegraphql.com/${person.id}/${size}; } }该模式在 examples/interfaces-inheritance/person/person.interface.ts 中有完整体现IPerson接口声明了带size参数的avatar字段并用throw new Error(Method not implemented.)占位Person类型examples/interfaces-inheritance/person/person.type.ts则继承并覆写了具体实现生成的 examples/interfaces-inheritance/schema.graphql 中可以看到avatar(size: Float!): String!已正确出现在接口与各实现类型上。接口在 schema 中的注册与 autoRegisterImplementations默认行为只要接口被显式用于 schema 定义作为 query/mutation 的返回类型或某个字段的类型所有实现该接口的对象类型都会自动注册进 schema无需额外配置。何时需要关闭自动注册以 Relay 体系的Node接口为例当同一套类型要暴露多个互相隔离的 schema如公开 schema 与私有 schema时默认的全量自动注册可能不合预期。此时可传入{ autoRegisterImplementations: false }InterfaceType({ autoRegisterImplementations: false }) abstract class Node { Field(type ID) id: string; }关闭后需要把希望在该 schema 中暴露的实现类显式加入buildSchema的orphanedTypes数组const schema await buildSchema({ resolvers, // Provide orphaned object types orphanedTypes: [Person, Animal, Recipe], });相关源码链路如下InterfaceType将autoRegisterImplementations false映射为元数据字段autoRegisteringDisabledsrc/decorators/InterfaceType.tsschema 生成器在buildOtherTypes中自动收集被使用接口的实现类时会先检查implementedInterfaceInfo.metadata.autoRegisteringDisabled若为true则跳过自动注册src/schema/schema-generator.ts 第 617-632 行而usedInterfaceTypes集合只在类型真正作为输出类型被引用时才会被写入同文件第 865 行的this.usedInterfaceTypes.add(interfaceType.target)orphanedTypes数组中的类对象/接口/输入类型会被filterTypesInfoByOrphanedTypesAndExtractType过滤并强制提取进最终 schemasrc/schema/schema-generator.ts 第 946-951 行。注意如果某个对象类型类被显式用作 GraphQL 类型例如Recipe作为addRecipemutation 的返回类型那么无论orphanedTypes如何设置它都会被注册进 schema。仓库测试 tests/functional/interfaces-and-inheritance.ts 第 1454-1506 行专门验证了这一行为InterfaceType({ autoRegisterImplementations: false })的接口在只提供orphanedTypes: [FirstSampleObject]时FirstSampleObject出现在 schema 中而未列出的SecondSampleObject不会出现。运行时类型解析resolveType默认要求当对象类型实现 GraphQL 接口时resolver 中必须返回类型类的真实实例如Object.assign(new Person(), ...)构造出的实例否则graphql-js无法正确判定底层 GraphQL 类型。schema 生成器默认的resolveType实现正是通过instance instanceof typeCls逐类匹配来识别类型src/schema/schema-generator.ts 第 497-504 行匹配失败会抛出InterfaceResolveTypeError见 src/errors/InterfaceResolveTypeError.ts。自定义 resolveType可以在InterfaceType选项中提供自己的resolveType函数从而允许 resolver 返回普通对象plain object运行时通过数据形状判断具体类型——这与 union 类型的做法一致InterfaceType({ resolveType: value { if (grades in value) { return Student; // 返回类型在 schema 中的名称字符串 } return Person; // 或返回对象类型类 }, }) abstract class IPerson { // ... }resolveType的返回值可以是类型名字符串也可以是对象类型类本身。源码中getResolveTypeFunction会对返回值做归一化字符串直接返回类则通过possibleObjectTypesInfo.find(objectType objectType.target resolvedType)映射为其 schema 名称src/schema/schema-generator.ts 第 925-936 行。此外传入的resolveType支持返回 Promise异步判定。需要提醒的是与 union 相比接口场景下自定义resolveType会更棘手因为实现同一接口的对象类型可能很多你未必能全部记住。仓库示例 examples/interfaces-inheritance/person/person.interface.ts 采用了resolveType: value value.constructor.name的通用兜底方案该文件注释中还标注了这是 issue #373 的 workaround。在 examples/interfaces-inheritance/resolver.ts 中可以看到配套做法personsquery 返回IPerson[]接口数组新增学生/员工时通过Object.assign(new Student(), ...)构造真实类实例从而保证类型可被正确解析。完整示例查询返回接口类型仓库的 examples/interfaces-inheritance 提供了接口继承的完整可运行示例核心结构包括person/person.interface.ts定义IPerson接口id、name、age字段 带size参数的avatar占位实现person/person.type.ts、student/student.type.ts、employee/employee.type.ts分别实现IPerson并扩展各自字段resolver.tspersonsquery 直接返回[IPerson]数组addStudent/addEmployeemutation 写入真实实例schema.graphql最终生成的 SDL展示了interface IPerson、type Person/Student/Employee implements IPerson以及带参数的avatar(size: Float!): String!字段对应的输入类型见 person/person.input.ts、student/student.input.ts、employee/employee.input.ts。功能测试位于 tests/functional/interfaces-and-inheritance.ts覆盖了接口实现、多接口实现、接口继承接口、字段合并覆盖、implements传非接口类型时报错、autoRegisterImplementations与orphanedTypes等场景可作为行为契约参考。小结在 TypeGraphQL 中定义 GraphQL 接口的要点可归纳为四条用抽象类 InterfaceType()声明接口形状用implements选项让对象类型或接口实现它字段装饰器可省略、可继承父类接口字段的 resolver 用方法体、Arg/Args或FieldResolver定义运行时要保证 resolver 返回类型类实例或为接口提供自定义resolveType。面对多 schema 隔离需求时再用autoRegisterImplementations: false与orphanedTypes精确控制暴露范围。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 接口类型Interface Type完全指南用抽象类定义 GraphQL 接口TypeGraphQL 接口类型Interface Type完全指南用抽象类定义 GraphQL 接口 TypeGraphQL 的核心设计理念是 用 T后端GraphQLAPI设计TypeGraphQL 接口类型Interfaces完整指南用抽象类与装饰器定义 GraphQL InterfaceTypeGraphQL 接口类型Interfaces完整指南用抽象类与装饰器定义 GraphQL Interface TypeGraphQL 的核心思想是后端GraphQLAPI设计TypeGraphQL 接口类型GraphQL Interface完整实战指南从 InterfaceType 定义到 resolveType 与 Schema 注册TypeGraphQL 接口类型GraphQL Interface完整实战指南从 InterfaceType 定义到 resolveType 与 Sch后端GraphQLAPI设计上一篇Kinto同步功能详解实现多设备数据无缝同步的终极指南下一篇3个维度解锁Windows隐藏能力ViVeTool深度探索指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表