
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);

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);

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);
});
});

作用域
@Injectable({ scope: Scope.DEFAULT }) // 单例(默认)
class DatabaseConnection {}
@Injectable({ scope: Scope.REQUEST }) // 每个请求一个新实例
class RequestContextService {}
@Injectable({ scope: Scope.TRANSIENT }) // 每次注入一个新实例
class TaskRunner {}
DI 容器在具有复杂依赖图的大型应用中表现出色。