尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TypeGraphQL 泛型类型(Generic Types)实战指南:用类工厂模式构建可复用的分页响应等泛型 GraphQL 类型

TypeGraphQL 泛型类型(Generic Types)实战指南:用类工厂模式构建可复用的分页响应等泛型 GraphQL 类型 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载在 TypeGraphQL 中类型继承通过把公共字段抽取到基类来减少代码重复但面对字段类型本身需要参数化的场景例如分页响应中的items: T[]固定的字段集合就不够用了。本文基于 TypeGraphQL 官方文档结合仓库源码与真实示例系统讲解如何借助类工厂class-creator模式在 TypeScript 中描述泛型 GraphQL 类型——从最基础的分页响应封装到支持标量等复杂类型参数、再到非抽象类工厂的类型注册与命名策略帮助你写出一套可复用、可扩展的泛型类型基础设施。为什么需要泛型类型GraphQL 类型系统本身不支持类型参数而 TypeScript 的泛型又仅存在于编译期。在 TypeGraphQL 中类型声明依赖装饰器元数据与emitDecoratorMetadata反射二者叠加后我们无法直接写出这样的代码// 这样写行不通装饰器无法感知 T 的具体类型 ObjectType() class PaginatedResponseT { Field(type [T]) items: T[]; }Field需要一个运行时值来确定字段的 GraphQL 类型而T是纯类型层面的符号运行时并不存在。因此TypeGraphQL 采用与Resolver 继承中相同的类工厂模式用一个工厂函数接收运行时类型参数在函数内部动态创建并返回一个装饰好的类。这样既保持了类 装饰器的声明式风格又实现了泛型化。基本用法封装一个泛型分页响应第一步定义工厂函数骨架先定义一个PaginatedResponse函数它在内部创建并返回一个abstract classexport default function PaginatedResponse() { abstract class PaginatedResponseClass { // ... } return PaginatedResponseClass; }为了让行为泛型化函数必须接收与类型参数对应的运行时参数。这里TItemClass是元素类型的类本身运行时存在它同时约束了类型参数TItemimport { type ClassType } from type-graphql; export default function PaginatedResponseTItem extends object(TItemClass: ClassTypeTItem) { abstract class PaginatedResponseClass { // ... } return PaginatedResponseClass; }ClassTypeT是 TypeGraphQL 提供的类型工具定义在 src/typings/utils/ClassType.ts本质是构造器 prototype的联合描述用于表达一个可实例化类的类型。第二步给工厂生成的类加装饰器工厂返回的类可以也应该像普通类一样使用装饰器且可以是ObjectType、InterfaceType或InputType中的任意一种具体取决于该泛型类型的用途export default function PaginatedResponseTItem extends object(TItemClass: ClassTypeTItem) { ObjectType() abstract class PaginatedResponseClass { // ... } return PaginatedResponseClass; }第三步在字段中混合泛型类型与运行时参数类内部按普通类的方式声明字段但类型引用和运行时引用可以同时出现import { Field, Int, type ClassType } from type-graphql; export default function PaginatedResponseTItem extends object(TItemClass: ClassTypeTItem) { ObjectType() abstract class PaginatedResponseClass { // 运行时参数传给 Field 作为 GraphQL 类型来源 Field(type [TItemClass]) // 泛型类型仅供 TypeScript 类型检查 items: TItem[]; Field(type Int) total: number; Field() hasMore: boolean; } return PaginatedResponseClass; }这里体现了本模式的核心技巧Field(type [TItemClass])中的TItemClass是运行时变量而items: TItem[]中的TItem是编译期类型参数。二者各司其职——装饰器负责生成 Schema类型注解负责 TypeScript 类型安全。第四步创建具体类型并接入 Resolver通过继承工厂返回值创建专用类型可以追加字段也可以重写父类字段的类型ObjectType() class PaginatedUserResponse extends PaginatedResponse(User) { // 添加更多字段 Field(type [String]) otherInfo: string[]; }然后在 Resolver 中直接作为返回类型使用Resolver() class UserResolver { Query() users(): PaginatedUserResponse { // 自定义业务逻辑取决于底层数据源与库 return { items, total, hasMore, otherInfo, }; } }生成的 GraphQL Schema 中会出现一个字段类型为[User!]!的PaginatedUserResponse对象类型。仓库中的完整可运行示例上述模式在仓库 examples/generic-types 中有完整的可运行实现paginated-response.type.ts 定义泛型工厂Field(_type [itemsFieldValue])等recipe.resolver.ts 中通过class RecipesResponse extends PaginatedResponse(Recipe)生成具体响应类型并在Query({ name: recipes })中返回{ items, hasMore, total }启动脚本 index.ts 使用buildSchema({ resolvers: [RecipeResolver], emitSchemaFile: ... })生成 Schema 并启动 Apollo Server监听 4000 端口schema.graphql 展示了最终产物type RecipesResponse { hasMore: Boolean! items: [Recipe!]! total: Int! }以及Query.recipes(first: Int 10): RecipesResponse!examples.graphql 给出了对应的查询与变更操作示例。复杂泛型类型值不止是类上面的例子要求元素必须是对象类型类ClassType。但在实际业务中items也可能是标量数组比如string[]。此时工厂函数的参数就不再是类而是任何可以传给Field的类型值——GraphQLScalarType、String、Number、Boolean等。只需要把参数类型签名放宽为联合类型并让类型参数TItemsFieldValue约束对应到标量值类型import { type ClassType, Field, ObjectType } from type-graphql; import type { GraphQLScalarType } from graphql; export default function PaginatedResponseTItemsFieldValue extends object( itemsFieldValue: ClassTypeTItemsFieldValue | GraphQLScalarType | String | Number | Boolean, ) { ObjectType() abstract class PaginatedResponseClass { Field(type [itemsFieldValue]) items: TItemsFieldValue[]; // ...其他字段 } return PaginatedResponseClass; }然后传入运行时标量值创建具体类型ObjectType() class PaginatedStringsResponse extends PaginatedResponsestring(String) { // ... }注意仓库 examples/generic-types/paginated-response.type.ts 中的写法是ClassTypeTItemsFieldValue | string | number | boolean效果等价——因为 TypeScript 的String包装对象在运行时与string基础类型引用指向同一构造函数Number/Boolean同理。这两种签名都可行选择哪种取决于你更习惯用包装对象还是基础类型字面量。Types factory不使用 abstract 的工厂模式前文推荐用abstract class是因为抽象类本身不会被注册进 Schema只有被具体子类继承后字段才会合并进子类型。这一点有测试佐证——tests/functional/generic-types.ts 中的用例shouldnt emit unused abstract object type验证了未被子类引用的抽象ObjectType基类不会出现在 Schema introspection 中。但工厂也可以返回非抽象类。区别在于非抽象类一旦被工厂创建就会立即注册到 Schema。因此这种方式创建的类不宜再作为基类去扩展字段因为基类本身已存在于 Schema容易造成类型重复/字段不一致必须为每个工厂实例提供唯一且可预测的类型名否则多个调用会因同名类型冲突而产生 Schema 构建错误。为此ObjectType接受一个字符串参数作为 Schema 中的类型名可用TItemClass.name动态拼出唯一名字export default function PaginatedResponseTItem extends object(TItemClass: ClassTypeTItem) { // 提供 Schema 中使用的唯一类型名 ObjectType(Paginated${TItemClass.name}Response) class PaginatedResponseClass { // ... } return PaginatedResponseClass; }由于此时类不是继承出来的需要把工厂结果存进变量并同时创建同名类型以便在 Resolver 里既做运行时值又做类型注解const PaginatedUserResponse PaginatedResponse(User); type PaginatedUserResponse InstanceTypetypeof PaginatedUserResponse; Resolver() class UserResolver { // 装饰器需要运行时值 Query(returns PaginatedUserResponse) users(): PaginatedUserResponse { // 实现同上文 } }两种工厂模式的取舍模式类是否立即注册进 Schema是否适合继续扩展命名要求abstract class工厂否只有被继承后才体现适合可在子类追加/重写字段无需手动指定子类名即类型名非abstract工厂是创建即注册不建议再扩展必须手动提供唯一类型名源码级佐证泛型工厂在测试中的完整形态tests/functional/generic-types.ts 是理解本特性底层行为的最佳教材其中覆盖了三种典型场景多子类共享同一泛型工厂ConnectionTItem工厂分别被UserConnection以const InstanceType方式使用与DogConnection以class extends方式使用消费测试断言 Schema 中同时存在UserConnection与DogConnection且items字段正确指向User/Dog对象类型子类添加新字段EdgeTNode工厂派生出RecipeEdge与FriendshipEdge各自追加personalNotes/friendedAt字段introspection 验证派生类型字段数为 3、类型名分别为Recipe/User、String/DateTimeISO子类重写基类字段类型子类Child用override baseField!: ChildSample重写工厂基类中的baseField类型测试确认 Schema 中该字段类型变为ChildSample类型向上兼容的重写是允许的。这些用例同时验证了查询执行结果——返回的数据结构与工厂/子类定义完全一致说明泛型工厂生成的类型在运行时和 Schema 两层都是真实可用的。使用建议与注意事项约束类型参数工厂的类型参数建议加上extends object约束元素为类时保证与ClassTypeT一致处理标量时再把约束放宽到对应基础类型装饰器类型要一致工厂基类与派生子类必须使用同类装饰器如都是ObjectType混用ObjectType与InputType会导致 Schema 构建错误——这与类型继承的规则一致参见 docs/inheritance.md字段赋值防御仓库示例中字段声明使用了!断言如items!: TItem[]因为工厂内部无法可靠地初始化泛型字段建议沿用这一写法命名冲突非 abstract 工厂务必用传入类名拼出唯一类型名如Paginated${TItemClass.name}Response避免多个调用撞名适用场景分页响应、Connection/Edge 结构、通用 CRUD 返回包装等结构固定、元素类型可变的模型是最适合泛型工厂的场景。小结TypeGraphQL 的泛型类型特性本质是用运行时传参的类工厂 编译期类型参数双轨并行的方式绕开 TypeScript 反射无法感知泛型参数的限制。掌握abstract class工厂可扩展、自动命名与非 abstract 工厂创建即注册、需手动命名两种模式后你就能把PaginatedResponseT、ConnectionT、EdgeT这类基础设施沉淀为可复用的类型工具让每个业务模型的分页、关联查询都免于重复声明。更完整的进阶示例请参考仓库 examples/generic-types并结合 tests/functional/generic-types.ts 理解其 Schema 层面的实际行为。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐WebPShop终极指南如何用免费Photoshop插件优化WebP图像格式WebPShop终极指南如何用免费Photoshop插件优化WebP图像格式 还在为Photoshop原生WebP支持功能不全而烦恼吗WebPShop作为一后端GraphQLAPI设计TypeGraphQL 泛型类型Generic Types实战指南用类工厂模式构建可复用的分页响应类型TypeGraphQL 泛型类型Generic Types实战指南用类工厂模式构建可复用的分页响应类型 本篇文章以 TypeGraphQL 0.17.0后端GraphQLAPI设计TypeGraphQL 泛型类型Generic Types实战指南用类工厂模式实现可复用的分页响应类型TypeGraphQL 泛型类型Generic Types实战指南用类工厂模式实现可复用的分页响应类型 TypeGraphQL 提供了一套基于 TypeS后端GraphQLAPI设计上一篇智能化文献管理工具Zotero Style让你的学术研究效率提升300%下一篇3步搞定番茄小说下载器离线阅读全平台解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表