正在加载,请稍候…

TypeScript 中的依赖注入:手动与容器化方案

在 TypeScript 中实现依赖注入,涵盖手动 DI、InversifyJS 和 NestJS。理解 IoC 容器、装饰器以及 DI 如何提升可测试性。

TypeScript 中的依赖注入:手动与容器化方案

TypeScript 中的依赖注入

DI 反转了依赖的创建控制权,使代码更易于测试和模块化。

手动 DI(组合根)

// 定义接口
interface UserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<User>;
}

interface EmailService {
  sendWelcome(email: string): Promise<void>;
}

// 实现
class PostgresUserRepository implements UserRepository { /* ... */ }
class SendgridEmailService implements EmailService { /* ... */ }
class MockEmailService implements EmailService {
  sentEmails: string[] = [];
  async sendWelcome(email: string) { this.sentEmails.push(email); }
}

// 注入依赖的服务
class UserService {
  constructor(
    private readonly userRepo: UserRepository,
    private readonly emailService: EmailService
  ) {}

  async register(email: string, password: string): Promise<User> {
    const user = await this.userRepo.save(User.create(email, password));
    await this.emailService.sendWelcome(email);
    return user;
  }
}

// 组合根(应用启动时)
const userRepo = new PostgresUserRepository(db);
const emailService = new SendgridEmailService(apiKey);
const userService = new UserService(userRepo, emailService);

// 测试中
const mockEmail = new MockEmailService();
const testUserService = new UserService(new InMemoryUserRepository(), mockEmail);

TypeScript 中的依赖注入:手动与容器化方案 插图

InversifyJS 容器

import { Container, injectable, inject } from 'inversify';

const TYPES = {
  UserRepository: Symbol('UserRepository'),
  EmailService: Symbol('EmailService'),
  UserService: Symbol('UserService'),
};

@injectable()
class PostgresUserRepository implements UserRepository { /* ... */ }

@injectable()
class SendgridEmailService implements EmailService { /* ... */ }

@injectable()
class UserService {
  constructor(
    @inject(TYPES.UserRepository) private userRepo: UserRepository,
    @inject(TYPES.EmailService) private emailService: EmailService
  ) {}
}

// 配置容器
const container = new Container();
container.bind<UserRepository>(TYPES.UserRepository).to(PostgresUserRepository);
container.bind<EmailService>(TYPES.EmailService).to(SendgridEmailService);
container.bind<UserService>(TYPES.UserService).to(UserService);

// 解析
const userService = container.get<UserService>(TYPES.UserService);

TypeScript 中的依赖注入:手动与容器化方案 插图

NestJS DI

// service.ts
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private userRepo: Repository<User>,
    private emailService: EmailService,
    private configService: ConfigService
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const user = this.userRepo.create(dto);
    const saved = await this.userRepo.save(user);
    await this.emailService.sendWelcome(saved.email);
    return saved;
  }
}

// module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserService, EmailService],
  exports: [UserService],
})
export class UserModule {}

// 使用 NestJS 测试
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';

describe('UserService', () => {
  let service: UserService;
  let mockEmailService: jest.Mocked<EmailService>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: EmailService,
          useValue: { sendWelcome: jest.fn() },
        },
        {
          provide: getRepositoryToken(User),
          useValue: { create: jest.fn(), save: jest.fn() },
        },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
    mockEmailService = module.get(EmailService);
  });
});

TypeScript 中的依赖注入:手动与容器化方案 插图

作用域

@Injectable({ scope: Scope.DEFAULT })   // 单例(默认)
class DatabaseConnection {}

@Injectable({ scope: Scope.REQUEST })   // 每个请求一个新实例
class RequestContextService {}

@Injectable({ scope: Scope.TRANSIENT }) // 每次注入一个新实例
class TaskRunner {}

DI 容器在具有复杂依赖图的大型应用中表现出色。