ARTICLE DETAIL

资讯详情

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

TypeGraphQL 自定义装饰器(Custom Decorators)完整实战指南:方法、类与参数三种模式及底层原理

TypeGraphQL 自定义装饰器(Custom Decorators)完整实战指南:方法、类与参数三种模式及底层原理 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载导读在 TypeGraphQL 中Query、Mutation、Arg、Ctx等内置装饰器帮助我们用类与装饰器的方式声明式地构建 GraphQL schema 与 resolver。但当多个 resolver 之间存在重复的校验、鉴权、参数注入等逻辑时就会产生大量样板代码。TypeGraphQL 提供了一整套自定义装饰器机制——包括方法装饰器Method Decorators、Resolver 类装饰器Resolver Class Decorators和参数装饰器Parameter Decorators——让我们可以把通用逻辑封装成语义化、可复用的装饰器直接应用于字段、整个 Resolver 类或单个方法参数。读完本文你将掌握三种自定义装饰器的创建方式、参数注入与Arg注册的进阶用法并通过仓库源码理解其底层实现原理。为什么需要自定义装饰器原文档指出自定义装饰器是减少样板代码、在 resolver 之间复用通用逻辑的利器。TypeGraphQL 支持三种自定义装饰器类型辅助函数返回类型作用范围方法装饰器createMethodMiddlewareDecoratorMethodDecorator单个字段/resolver 方法Resolver 类装饰器createResolverClassMiddlewareDecoratorClassDecorator整个 Resolver 类作用于该类下所有 Query/Mutation参数装饰器createParameterDecoratorParameterDecorator单个方法参数可向方法注入返回值这三种辅助函数均从type-graphql包导出在仓库中位于 src/decorators 目录对应源码文件为 createMethodMiddlewareDecorator.ts、createResolverClassMiddlewareDecorator.ts 与 createParameterDecorator.ts。方法装饰器用中间件逻辑封装语义化装饰器方法装饰器本质上是中间件middleware的语法糖封装。TypeGraphQL 的中间件机制详见 middlewares.md它允许复用 resolver 间的公共代码而自定义方法装饰器在此基础上进一步收敛 API。创建方式调用createMethodMiddlewareDecorator辅助函数传入中间件逻辑MiddlewareFn并返回其结果export function ValidateArgs(schema: JoiSchema) { return createMethodMiddlewareDecorator(async ({ args }, next) { // 中间件代码可使用自定义装饰器传入的参数 // 例如基于 joi schema 的校验逻辑 await joiValidate(schema, args); return next(); }); }从源码看createMethodMiddlewareDecorator的实现极为简洁——它直接返回内置的UseMiddleware装饰器// src/decorators/createMethodMiddlewareDecorator.ts export function createMethodMiddlewareDecoratorTContextType extends object object( resolver: MiddlewareFnTContextType, ): MethodDecorator { return UseMiddleware(resolver); }也就是说自定义方法装饰器 UseMiddleware(中间件函数)的再封装。中间件签名MiddlewareFnTContext接收两个参数action: ResolverDataTContext包含root、args、context、info与next: NextFn定义见 src/typings/middleware.ts。使用方法将自定义装饰器放在 resolver/字段上方并传入所需参数还可以与内置的UseMiddleware混用Resolver() export class RecipeResolver { ValidateArgs(MyArgsSchema) // 自定义装饰器 UseMiddleware(ResolveTime) // 显式中间件 Query() randomValue(Args() { scale }: MyArgs): number { return Math.random() * scale; } }注意装饰器的执行顺序TypeGraphQL 会按装饰器声明顺序自上而下收集并执行中间件即ValidateArgs的校验先于ResolveTime的计时执行。Resolver 类装饰器一次声明作用于整个类与方法装饰器类似我们可以创建作用于整个 Resolver 类的自定义装饰器。此时需要调用createResolverClassMiddlewareDecoratorexport function ValidateArgs(schema: JoiSchema) { return createResolverClassMiddlewareDecorator(async ({ args }, next) { // 中间件代码可使用自定义装饰器传入的参数 // 例如基于 joi schema 的校验逻辑 await joiValidate(schema, args); return next(); }); }其用法与方法装饰器几乎一致只是装饰器被放置于 Resolver 类之上ValidateArgs(MyArgsSchema) // 自定义装饰器 UseMiddleware(ResolveTime) // 显式中间件 Resolver() export class RecipeResolver { Query() randomValue(Args() { scale }: MyArgs): number { return Math.random() * scale; } }这样我们只需在代码中放置一次自定义装饰器就会被应用到该 Resolver 的所有 Query 与 Mutation 上。底层原理同样清晰createResolverClassMiddlewareDecorator也只是对UseMiddleware的封装唯一区别是返回类型为ClassDecorator// src/decorators/createResolverClassMiddlewareDecorator.ts export function createResolverClassMiddlewareDecoratorTContextType extends object object( resolver: MiddlewareFnTContextType, ): ClassDecorator { return UseMiddleware(resolver); }UseMiddleware本身是一个同时支持类与方法的重载装饰器UseMiddleware.ts当propertyKey为空装饰在类上时调用collectResolverMiddlewareMetadata将该中间件注册到整个 Resolver 类当装饰在方法上时调用collectMiddlewareMetadata仅注册到该字段。因此同一个UseMiddleware才能既被方法装饰器复用也被类装饰器复用。参数装饰器向方法注入返回值参数装饰器与中间件最大的不同在于它有能力返回一个值并注入到方法的对应参数中。这有效减少了原先通过污染context来在中间件与 resolver 之间通信的变通做法。参数装饰器可以只是一个简单的数据提取函数让 resolver 更易于单元测试function CurrentUser() { return createParameterDecoratorMyContextType(({ context }) context.currentUser); }也可以更高级封装部分计算逻辑。相比中间件参数装饰器对代码执行时机有更细粒度的控制——例如只在真正被请求时才基于 GraphQL info 计算字段映射通过Fields()装饰器触发function Fields(level 1): ParameterDecorator { return createParameterDecorator(async ({ info }) { const fieldsMap: FieldsMap {}; // 基于 GraphQL 解析器的 info 参数与 level 参数 // 计算请求字段的对象信息 // 甚至可以调用异步服务因为它是普通 async 函数可以直接 await return fieldsMap; }); }性能提醒原文档强调自定义参数装饰器的逻辑若是async函数可能会拖慢 GraphQL resolver 的执行速度因此如无必要应尽量使用同步逻辑。参数装饰器在 resolver 中的用法与内置装饰器Args、Arg、Ctx等完全一致其返回值会直接作为对应参数的实参Resolver() export class RecipeResolver { constructor(private readonly recipesRepository: RepositoryRecipe) {} Authorized() Mutation(returns Recipe) async addRecipe( Args() recipeData: AddRecipeInput, // 自定义装饰器用法与内置装饰器一致 CurrentUser() currentUser: User, ) { const recipe: Recipe { ...recipeData, // 在 resolver 代码中使用自定义装饰器返回的数据 author: currentUser, }; await this.recipesRepository.save(recipe); return recipe; } Query(returns Recipe, { nullable: true }) async recipe( Arg(id) id: string, // 从 GraphQL 查询 info 中解析字段的自定义装饰器 Fields() fields: FieldsMap, ) { return await this.recipesRepository.find(id, { // 将字段映射作为 select 投影以优化数据库查询 select: fields, }); } }从源码层面看createParameterDecorator返回的装饰器在调用时会将元数据收集到 metadata storage// src/decorators/createParameterDecorator.ts getMetadataStorage().collectHandlerParamMetadata({ kind: custom, target: prototype.constructor, methodName: propertyKey, index: parameterIndex, resolver, options, });其中kind: custom对应的元数据结构为CustomParamMetadata见 src/metadata/definitions/param-metadata.ts它保存了resolver函数与可选参数注册选项。最终在 schema 生成阶段src/resolvers/create.ts 的createHandlerResolver中会先执行中间件链再由getParams依次解析各参数的元数据、调用自定义参数 resolver 获取值最后通过targetInstance[methodName].apply(targetInstance, params)调用 resolver 方法——这正是参数注入得以实现的底层执行链路。自定义Arg装饰器注册并暴露 GraphQL 参数某些场景下我们希望自定义装饰器不仅能注入参数值还能在 GraphQL schema 中注册/暴露一个参数。此时如果在一个自定义装饰器内同时调用Arg()与createParameterDecorator()会与 TypeGraphQL 内部机制产生冲突。为此createParameterDecorator()支持第二个参数CustomParameterOptions其中arg键用于提供Arg的元数据实现一举两得function RandomIdArg(argName id) { return createParameterDecorator( // 在此实现取用户提供的参数或生成随机 id的逻辑 ({ args }) args[argName] ?? Math.round(Math.random() * MAX_ID_VALUE), { // 在此提供元数据将参数注册为 GraphQL 参数 arg: { name: argName, typeFunc: () Int, options: { nullable: true, description: Accepts provided id or generates a random one., }, }, }, ); }CustomParameterOptions的类型定义arg选项包含name、typeFunc与可选的options: ArgOptions见 src/decorators/createParameterDecorator.ts其中ArgOptions合并了类型选项、描述、校验与废弃标记等配置定义见 src/decorators/Arg.ts。从源码可以看到当传入paramOptions.arg时createParameterDecorator内部会调用与Arg相同的getParamInfo辅助函数src/helpers/params.ts通过design:paramtypes反射元数据或typeFunc推断 GraphQL 类型并把这些信息一并存入options.arg——这正是自定义Arg装饰器能正确生成 schema 参数的原因。使用时与普通的Arg装饰器几乎无差别Resolver() export class RecipeResolver { constructor(private readonly recipesRepository: RepositoryRecipe) {} Query(returns Recipe, { nullable: true }) async recipe( // 自定义装饰器在 schema 中暴露一个 arg RandomIdArg(id) id: number, ) { return await this.recipesRepository.findById(id); } }小提示arg.options同样支持传入validateFn自定义校验函数。仓库示例 random-id-arg.ts 中就对生成/传入的 id 做了取值范围校验validateFn会在值超出[0, MAX_ID_VALUE]时抛出错误。仓库实战示例middlewares-custom-decorators原文档末尾提供了配套示例项目。在仓库中完整的可运行示例位于 examples/middlewares-custom-decorators包含三种自定义装饰器的真实实现decorators/validate-args.ts基于class-validator的ValidateArgs方法装饰器。它接收一个ClassTypeT将args实例化后调用validate()若存在校验错误则抛出ArgumentValidationError否则继续执行next()。注释明确指出示例使用class-validator你也可以换成joi或任意校验库。decorators/current-user.ts极简参数装饰器从context中提取currentUser泛型参数显式标注为Context类型。decorators/random-id-arg.ts自定义Arg装饰器的完整实现包含arg元数据与validateFn校验。decorators/index.ts统一导出上述装饰器。在 recipe/recipe.resolver.ts 中可以看到它们的组合用法类级使用UseMiddleware(ResolveTimeMiddleware)recipes查询上叠加ValidateArgs(RecipesArgs)参数类见 recipe.args.ts包含Min(0)的skip与Min(1) Max(50)的take并同时使用RandomIdArg(id)与CurrentUser()两个参数装饰器。运行示例项目即可直观观察装饰器对查询入参校验、执行耗时统计与上下文注入的实际效果。总结自定义装饰器是 TypeGraphQL 将可复用逻辑与声明式 API结合的精华特性方法装饰器createMethodMiddlewareDecorator把中间件逻辑封装成语义化装饰器应用于单个字段Resolver 类装饰器createResolverClassMiddlewareDecorator把同样的逻辑提升到类级别一次声明、作用于该类所有 Query/Mutation参数装饰器createParameterDecorator直接向方法参数注入返回值比中间件更精细地控制执行时机且天然友好于单元测试自定义Arg装饰器借助CustomParameterOptions的arg键在注入参数值的同时把参数注册进 GraphQL schema替代内部同时调用Arg()与createParameterDecorator()的冲突写法。从源码看方法/类装饰器本质都是UseMiddleware的封装createMethodMiddlewareDecorator.ts、createResolverClassMiddlewareDecorator.ts而参数装饰器则通过 metadata storage 记录kind: custom的参数元数据由 src/resolvers/create.ts 在 resolver 执行前统一解析注入。掌握这一机制后你可以将校验、鉴权、字段投影、随机参数生成等通用逻辑沉淀为项目内部的专属 DSL让 resolver 代码保持干净、可读且易于测试。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 自定义装饰器Custom Decorators实战指南方法装饰器与参数装饰器TypeGraphQL 自定义装饰器Custom Decorators实战指南方法装饰器与参数装饰器 导读 TypeGraphQL 允许开发者用 Type后端GraphQLAPI设计TypeGraphQL 自定义装饰器完全指南Method / Class / Parameter 三种 Decorator 的实战与源码解析TypeGraphQL 自定义装饰器完全指南Method / Class / Parameter 三种 Decorator 的实战与源码解析 TypeGrap后端GraphQLAPI设计Draft.js 装饰器Decorators完全指南从 CompositeDecorator 到自定义装饰器Draft.js 装饰器Decorators完全指南从 CompositeDecorator 到自定义装饰器 Draft.js 除了内联样式Inline前端UI组件上一篇Neorg与纳米材料性能测试实验方法与结果分析下一篇Go泛型陷阱为什么构造函数是90%类型错误的根源创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表