You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS依赖注入中如何让编译器自动识别泛型类型?

Zod封装NestJS缓存模块:自动推导泛型注入的解决方案

问题背景

基于NestJS原生缓存模块封装了带Zod校验的CachingModule,通过传入Zod Schema实现缓存条目序列化/反序列化的类型安全。当前CachingModule.register(cacheSchema)会生成对应泛型的CachingService<T>实例,但注入时必须手动指定CachingService<typeof cacheSchema>,编译器无法自动推导泛型类型。若移除泛型改用静态导入Schema,会导致测试与Schema强耦合,不利于扩展。

解决方案

方案1:多Schema场景 - 自定义令牌+forFeature模式

适合一个应用中需要多个不同缓存Schema的场景,每个Schema对应独立的服务实例,避免类型冲突。

实现步骤

  1. 生成绑定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}`);
}
  1. 改造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],
    };
  }
}
  1. 注入时自动推导泛型
    在模块中导入对应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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 02:47:28