Skip to content

Provider 的多种写法:useClassuseValueuseFactoryuseExisting

在 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 使用场景

  1. 接口与实现分离
typescript
// 在测试环境中使用 Mock 实现
@Module({
  providers: [
    {
      provide: 'IUserService',
      useClass: process.env.NODE_ENV === 'test' 
        ? MockUserService 
        : UserService,
    },
  ],
})
export class UserModule {}
  1. 条件性实现
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 使用场景

  1. 配置对象
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 {}
  1. 常量定义
typescript
export const MESSAGES = {
  USER_NOT_FOUND: 'User not found',
  INVALID_CREDENTIALS: 'Invalid credentials',
};

@Module({
  providers: [
    {
      provide: 'MESSAGES',
      useValue: MESSAGES,
    },
  ],
})
export class SharedModule {}
  1. 外部库实例
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 使用场景

  1. 动态配置
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 {}
  1. 条件性创建
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 {}
  1. 异步初始化
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 使用场景

  1. 接口别名
typescript
@Injectable()
export class UserService implements IUserService {
  findAll() {
    return ['user1', 'user2'];
  }
}

@Module({
  providers: [
    UserService,
    {
      provide: 'IUserService',
      useExisting: UserService,
    },
  ],
})
export class UserModule {}
  1. 简化注入
typescript
// 长名称简化
@Module({
  providers: [
    VeryLongAndComplicatedServiceName,
    {
      provide: 'SimpleService',
      useExisting: VeryLongAndComplicatedServiceName,
    },
  ],
})
export class AppModule {}

6. Provider 选择指南

6.1 选择标准

Provider 类型使用场景性能灵活性
useClass标准服务实现中等中等
useValue静态配置、常量
useFactory动态创建、条件创建
useExisting别名、接口实现中等

6.2 最佳实践

  1. 优先使用 useClass
typescript
// 推荐
@Module({
  providers: [UserService],
})

// 而不是
@Module({
  providers: [{
    provide: UserService,
    useClass: UserService,
  }],
})
  1. 合理使用 useValue
typescript
// 配置对象使用 useValue
const config = {
  apiUrl: process.env.API_URL,
  timeout: 5000,
};

@Module({
  providers: [
    {
      provide: 'APP_CONFIG',
      useValue: config,
    },
  ],
})
  1. 谨慎使用 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 类型各有特点:

  1. useClass:标准的类 Provider,适用于大多数服务场景
  2. useValue:直接提供值,适用于配置对象和常量
  3. useFactory:通过工厂函数创建,适用于动态和条件性创建
  4. useExisting:为现有 Provider 创建别名,适用于接口实现和简化注入

理解每种 Provider 类型的特点和使用场景,有助于我们:

  1. 选择合适的 Provider 类型
  2. 设计灵活的依赖注入结构
  3. 优化应用性能
  4. 提高代码可维护性

在下一篇文章中,我们将探讨自定义 Provider Token:字符串 vs Symbol vs 类型,了解为什么推荐用 InjectionToken 以及如何避免命名冲突。