Provider 的多种写法:useClass、useValue、useFactory、useExisting
在 NestJS 中,Provider 是依赖注入系统的核心概念之一。虽然我们经常使用简单的类作为 Provider,但实际上 NestJS 提供了多种定义 Provider 的方式,每种方式都有其特定的使用场景和优势。本文将深入探讨这四种 Provider 类型的内部机制和最佳实践。
1. Provider 基础概念
1.1 什么是 Provider?
Provider 是 NestJS 中可以被注入到其他组件(如控制器、服务等)中的对象。它可以是一个服务、一个值、一个工厂函数的返回值,或者是一个现有 Provider 的别名。
typescript
// 最简单的 Provider - 类 Provider
@Injectable()
export class UserService {
findAll() {
return ['user1', 'user2'];
}
}
@Module({
providers: [UserService], // 简写形式
})
export class UserModule {}1.2 Provider 的完整形式
typescript
// 完整的 Provider 定义
interface Provider {
provide: any;
useClass?: Type<any>;
useValue?: any;
useFactory?: (...args: any[]) => any;
useExisting?: any;
inject?: Array<Type<any> | string | symbol | Abstract<any> | Function>;
scope?: Scope;
}2. useClass Provider
2.1 基本用法
useClass Provider 是最常见的形式,它告诉 NestJS 使用指定的类来创建实例:
typescript
// 接口定义
interface IUserService {
findAll(): any[];
}
// 实现类
@Injectable()
export class UserService implements IUserService {
findAll() {
return ['user1', 'user2'];
}
}
@Injectable()
export class MockUserService implements IUserService {
findAll() {
return ['mock-user1', 'mock-user2'];
}
}
// 在模块中使用
@Module({
providers: [
{
provide: 'IUserService', // Token
useClass: UserService, // 实际使用的类
},
],
})
export class UserModule {}2.2 内部实现机制
typescript
// 简化的 useClass 处理过程
class ProviderResolver {
private handleClassProvider(provider: ClassProvider, wrapper: InstanceWrapper) {
// 1. 设置元类型
wrapper.metatype = provider.useClass;
// 2. 设置注入 token
if (provider.provide) {
wrapper.token = provider.provide;
} else {
wrapper.token = provider.useClass;
}
// 3. 解析依赖
this.resolveDependencies(wrapper);
}
}2.3 使用场景
- 接口与实现分离:
typescript
// 在测试环境中使用 Mock 实现
@Module({
providers: [
{
provide: 'IUserService',
useClass: process.env.NODE_ENV === 'test'
? MockUserService
: UserService,
},
],
})
export class UserModule {}- 条件性实现:
typescript
// 根据配置选择不同的数据库实现
@Module({
providers: [
{
provide: 'DatabaseService',
useClass: config.useMongo
? MongoDatabaseService
: SqlDatabaseService,
},
],
})
export class DatabaseModule {}3. useValue Provider
3.1 基本用法
useValue Provider 直接提供一个值,不需要实例化:
typescript
// 配置对象
const appConfig = {
apiUrl: 'https://api.example.com',
maxRetries: 3,
timeout: 5000,
};
@Module({
providers: [
{
provide: 'APP_CONFIG',
useValue: appConfig,
},
],
})
export class ConfigModule {}
// 使用配置
@Injectable()
export class ApiService {
constructor(
@Inject('APP_CONFIG') private config: typeof appConfig,
) {}
}3.2 内部实现机制
typescript
// 简化的 useValue 处理过程
class ProviderResolver {
private handleValueProvider(provider: ValueProvider, wrapper: InstanceWrapper) {
// 1. 直接设置实例
wrapper.instance = provider.useValue;
// 2. 标记为已解析
wrapper.isResolved = true;
// 3. 设置 token
wrapper.token = provider.provide;
}
}3.3 使用场景
- 配置对象:
typescript
const databaseConfig = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USER,
};
@Module({
providers: [
{
provide: 'DATABASE_CONFIG',
useValue: databaseConfig,
},
],
})
export class DatabaseModule {}- 常量定义:
typescript
export const MESSAGES = {
USER_NOT_FOUND: 'User not found',
INVALID_CREDENTIALS: 'Invalid credentials',
};
@Module({
providers: [
{
provide: 'MESSAGES',
useValue: MESSAGES,
},
],
})
export class SharedModule {}- 外部库实例:
typescript
import * as fs from 'fs';
@Module({
providers: [
{
provide: 'FILE_SYSTEM',
useValue: fs,
},
],
})
export class FileModule {}4. useFactory Provider
4.1 基本用法
useFactory Provider 使用工厂函数动态创建值:
typescript
// 工厂函数
function createDatabaseConnection(config: ConfigService) {
return new DatabaseConnection({
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
});
}
@Module({
providers: [
ConfigService,
{
provide: 'DATABASE_CONNECTION',
useFactory: createDatabaseConnection,
inject: [ConfigService], // 指定工厂函数依赖
},
],
})
export class DatabaseModule {}4.2 内部实现机制
typescript
// 简化的 useFactory 处理过程
class ProviderResolver {
private async handleFactoryProvider(provider: FactoryProvider, wrapper: InstanceWrapper) {
// 1. 设置工厂函数
wrapper.metatype = provider.useFactory;
// 2. 解析工厂函数依赖
if (provider.inject) {
wrapper.inject = provider.inject;
await this.resolveFactoryDependencies(wrapper);
}
// 3. 设置 token
wrapper.token = provider.provide;
}
private async resolveFactoryDependencies(wrapper: InstanceWrapper) {
const dependencies = [];
for (const token of wrapper.inject) {
const dependency = await this.resolveDependency(token);
dependencies.push(dependency);
}
wrapper.dependencies = dependencies;
}
}4.3 使用场景
- 动态配置:
typescript
function createJwtOptions(config: ConfigService) {
return {
secret: config.get('JWT_SECRET'),
expiresIn: config.get('JWT_EXPIRES_IN'),
};
}
@Module({
providers: [
ConfigService,
{
provide: 'JWT_OPTIONS',
useFactory: createJwtOptions,
inject: [ConfigService],
},
],
})
export class AuthModule {}- 条件性创建:
typescript
function createLogger(config: ConfigService) {
if (config.get('NODE_ENV') === 'production') {
return new ProductionLogger();
}
return new DevelopmentLogger();
}
@Module({
providers: [
ConfigService,
{
provide: 'LOGGER',
useFactory: createLogger,
inject: [ConfigService],
},
],
})
export class LoggerModule {}- 异步初始化:
typescript
async function createRedisClient(config: ConfigService) {
const client = new Redis({
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
});
await client.connect();
return client;
}
@Module({
providers: [
ConfigService,
{
provide: 'REDIS_CLIENT',
useFactory: createRedisClient,
inject: [ConfigService],
},
],
})
export class RedisModule {}5. useExisting Provider
5.1 基本用法
useExisting Provider 为现有 Provider 创建别名:
typescript
@Injectable()
export class RealUserService {
findAll() {
return ['user1', 'user2'];
}
}
@Module({
providers: [
RealUserService,
{
provide: 'UserServiceAlias',
useExisting: RealUserService, // 创建别名
},
],
})
export class UserModule {}5.2 内部实现机制
typescript
// 简化的 useExisting 处理过程
class ProviderResolver {
private handleExistingProvider(provider: ExistingProvider, wrapper: InstanceWrapper) {
// 1. 设置别名指向
wrapper.useExisting = provider.useExisting;
// 2. 设置 token
wrapper.token = provider.provide;
// 3. 标记为需要解析现有 Provider
wrapper.isAlias = true;
}
}5.3 使用场景
- 接口别名:
typescript
@Injectable()
export class UserService implements IUserService {
findAll() {
return ['user1', 'user2'];
}
}
@Module({
providers: [
UserService,
{
provide: 'IUserService',
useExisting: UserService,
},
],
})
export class UserModule {}- 简化注入:
typescript
// 长名称简化
@Module({
providers: [
VeryLongAndComplicatedServiceName,
{
provide: 'SimpleService',
useExisting: VeryLongAndComplicatedServiceName,
},
],
})
export class AppModule {}6. Provider 选择指南
6.1 选择标准
| Provider 类型 | 使用场景 | 性能 | 灵活性 |
|---|---|---|---|
| useClass | 标准服务实现 | 中等 | 中等 |
| useValue | 静态配置、常量 | 高 | 低 |
| useFactory | 动态创建、条件创建 | 低 | 高 |
| useExisting | 别名、接口实现 | 高 | 中等 |
6.2 最佳实践
- 优先使用 useClass:
typescript
// 推荐
@Module({
providers: [UserService],
})
// 而不是
@Module({
providers: [{
provide: UserService,
useClass: UserService,
}],
})- 合理使用 useValue:
typescript
// 配置对象使用 useValue
const config = {
apiUrl: process.env.API_URL,
timeout: 5000,
};
@Module({
providers: [
{
provide: 'APP_CONFIG',
useValue: config,
},
],
})- 谨慎使用 useFactory:
typescript
// 复杂初始化使用 useFactory
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
const connection = new DatabaseConnection();
await connection.connect();
return connection;
},
},
],
})7. 高级用法
7.1 组合使用
typescript
@Module({
providers: [
ConfigService,
{
provide: 'DATABASE_CONFIG',
useFactory: (config: ConfigService) => ({
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
}),
inject: [ConfigService],
},
{
provide: 'DATABASE_CONNECTION',
useFactory: async (dbConfig: any) => {
const connection = new DatabaseConnection(dbConfig);
await connection.connect();
return connection;
},
inject: ['DATABASE_CONFIG'],
},
],
})
export class DatabaseModule {}7.2 自定义 Provider 工厂
typescript
// 创建 Provider 工厂函数
function createConfigProvider(env: string) {
return {
provide: 'APP_CONFIG',
useValue: {
isDevelopment: env === 'development',
isProduction: env === 'production',
},
};
}
@Module({
providers: [
createConfigProvider(process.env.NODE_ENV),
],
})
export class ConfigModule {}8. 总结
NestJS 提供的四种 Provider 类型各有特点:
- useClass:标准的类 Provider,适用于大多数服务场景
- useValue:直接提供值,适用于配置对象和常量
- useFactory:通过工厂函数创建,适用于动态和条件性创建
- useExisting:为现有 Provider 创建别名,适用于接口实现和简化注入
理解每种 Provider 类型的特点和使用场景,有助于我们:
- 选择合适的 Provider 类型
- 设计灵活的依赖注入结构
- 优化应用性能
- 提高代码可维护性
在下一篇文章中,我们将探讨自定义 Provider Token:字符串 vs Symbol vs 类型,了解为什么推荐用 InjectionToken 以及如何避免命名冲突。