NestJS依赖注入中如何让编译器自动识别泛型类型?
Zod封装NestJS缓存模块:自动推导泛型注入的解决方案
问题背景
基于NestJS原生缓存模块封装了带Zod校验的CachingModule,通过传入Zod Schema实现缓存条目序列化/反序列化的类型安全。当前CachingModule.register(cacheSchema)会生成对应泛型的CachingService<T>实例,但注入时必须手动指定CachingService<typeof cacheSchema>,编译器无法自动推导泛型类型。若移除泛型改用静态导入Schema,会导致测试与Schema强耦合,不利于扩展。
解决方案
方案1:多Schema场景 - 自定义令牌+forFeature模式
适合一个应用中需要多个不同缓存Schema的场景,每个Schema对应独立的服务实例,避免类型冲突。
实现步骤
- 生成绑定Schema的注入令牌
创建泛型函数,为每个Schema生成唯一注入令牌,并绑定对应的CachingService<T>类型:
import { InjectionToken } from '@nestjs/common'; import { ZodSchema } from 'zod'; export function createCachingToken<T>(schema: ZodSchema<T>): InjectionToken<CachingService<T>> { // 用Schema的描述或类型名生成唯一标识,避免冲突 return Symbol(`CachingService:${schema.description || schema._def.typeName}`); }
- 改造CachingModule为forFeature模式
将原register方法改为forFeature,注册对应令牌的服务实例:
import { Module, DynamicModule, Cache } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { ZodSchema } from 'zod'; @Module({}) export class CachingModule { static forFeature<T>(schema: ZodSchema<T>): DynamicModule { const token = createCachingToken(schema); return { module: CachingModule, providers: [ { provide: token, useFactory: (cacheManager: Cache) => new CachingService<T>(cacheManager, schema), inject: [CACHE_MANAGER], }, ], exports: [token], }; } }
- 注入时自动推导泛型
在模块中导入对应Schema的forFeature,用@Inject传入生成的令牌,编译器自动根据Schema推导泛型:
// 模块导入 @Module({ imports: [CachingModule.forFeature(userCacheSchema)], }) export class UserModule {} // 服务注入 @Injectable() export class UserService { constructor( @Inject(createCachingToken(userCacheSchema)) private readonly cachingService: CachingService // 自动推导为CachingService<typeof userCacheSchema> ) {} }
方案2:简化注入 - 自定义装饰器封装令牌
基于方案1,用自定义装饰器简化注入代码:
import { Inject } from '@nestjs/common'; import { ZodSchema } from 'zod'; export function InjectCaching<T>(schema: ZodSchema<T>) { return Inject(createCachingToken(schema)); }
注入时直接使用该装饰器,泛型自动推导:
@Injectable() export class UserService { constructor( @InjectCaching(userCacheSchema) private readonly cachingService: CachingService ) {} }
方案3:单Schema场景 - 改进模块注册的类型推导
如果应用中只需要一个缓存Schema,可直接改进CachingModule.register的返回类型,让编译器自动关联泛型:
@Module({}) export class CachingModule { static register<T>(schema: ZodSchema<T>): DynamicModule & { exports: [typeof CachingService<T>]; } { return { module: CachingModule, providers: [ { provide: CachingService, useFactory: (cacheManager: Cache) => new CachingService<T>(cacheManager, schema), inject: [CACHE_MANAGER], }, ], exports: [CachingService], } as DynamicModule & { exports: [typeof CachingService<T>]; }; } }
导入模块后,注入时直接使用CachingService即可自动推导泛型:
@Module({ imports: [CachingModule.register(globalCacheSchema)], }) export class AppModule {} @Injectable() export class AppService { constructor(private readonly cachingService: CachingService) {} // 自动推导为对应泛型 }
关键优势
- 所有方案均无需手动指定泛型参数,编译器自动推导类型;
- 测试时可传入mock的Zod Schema,避免与业务Schema强耦合;
- 多Schema场景支持独立服务实例,类型隔离;单Schema场景保持服务单例,简化配置。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

