自定义提供者
在早期的章节中,我们涉及了**依赖注入(DI)**的各个方面,以及它在 Nest 中的使用方式。 其中一个例子是使用构造函数注入将实例(通常是服务提供者)注入到类中。 您可能不会感到惊讶地了解到,依赖注入在 Nest 核心中以一种基本的方式内置。 到目前为止,我们只探讨了一个主要的模式。随着应用程序变得更加复杂,您可能需要充分利用 DI 系统的全部功能, 因此让我们更详细地探讨它们。
依赖注入基础知识
依赖注入是一种**控制反转(IoC)**技术,其中您将依赖项的实例化委托给IoC容器 (在我们的情况下是 NestJS 运行时系统),而不是在您自己的代码中以命令式的方式执行。 让我们来看一下来自提供者章节的这个例子中正在发生什么。
首先,我们定义了一个提供者。@Injectable() 装饰器将 CatsService 类标记为提供者。
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
findAll(): Cat[] {
return this.cats;
}
}
然后,我们请求Nest将提供者程序注入到我们的控制器类中:
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
最后,我们向Nest IoC容器注册提供者程序:
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
究竟是什么在暗中发生,使得这一切得以实现?这个过程有三个关键步骤:
- 在
cats.service.ts中,我们使用@Injectable()装饰器将CatsService类声明为可由Nest IoC容器管理的类。 - 在
cats.controller.ts中,CatsController通过构造器注入声明了对CatsService标记的依赖。constructor(private catsService: CatsService) {} - 在
app.module.ts中,我们将字符CatsService与cats.service.ts文件中的CatsService类关联起来。
我们将在下面详细了解这种关联(也称为注册)是如何发生的。
当Nest IoC容器实例化CatsController时,它首先查找任何依赖项。
当它找到CatsService依赖项时,
它会在CatsService标记上执行查找,该标记返回CatsService类,
根据注册步骤(上述的#3)。假设是SINGLETON范围(默认行为),Nest将创建一个CatsService实例,
将其缓存并返回,或者如果已经缓存,则返回现有实例。
这个解释有点简化以阐明观点。我们忽略的一个重要领域是分析代码以查找依赖项的过程非常复杂,并且发生在应用程序启动期间。 一个关键特征是依赖性分析(或“创建依赖图”)是传递的。 在上述示例中,如果CatsService本身有依赖关系,这些依赖关系也将被解决。 依赖图确保以正确的顺序 解决依赖关系 - 本质上是“自底向上”。 这种机制使开发人员无需管理如此复杂的依赖关系图。
标准提供者
让我们仔细看看@Module()装饰器。在app.module中,我们声明:
@Module({
controllers: [CatsController],
providers: [CatsService],
})
providers属性采用一组提供者。
到目前为止,我们已经通过类名列表提供了这些提供者程序。
事实上,语法providers: [CatsService]是更完整语法的简写形式:
providers: [
{
provide: CatsService,
useClass: CatsService,
},
]
现在我们看到了这个显式结构,就能理解注册过程了。
在这里,我们明确地将CatsService标记(token)与CatsService类关联起来。
这种简称只是为了简化最常见的使用情况,即使用标记来请求同名类的实例。
自定义提供程序
当你的需求超出标准提供程序提供的范围时会发生什么呢?以下是一些例子:
- 你想要创建一个自定义实例,而不是让Nest实例化(或返回缓存的实例)一个类
- 你想要在第二个依赖中重用一个现有类
- 你想要用测试的模拟版本覆盖一个类 Nest允许你定义自定义提供程序来处理这些情况。它提供了几种定义自定义提供程序的方式。 让我们逐步了解它们。
如果你在依赖项解析方面遇到问题,你可以设置NEST_DEBUG环境变量,在启动期间获得额外的依赖项解析日志。
Value提供程序: useValue
useValue语法对于注入常量值、将外部库放入Nest容器中或用模拟对象替换真实实现非常有用。
比如说,你想要强制Nest在测试目的中使用一个模拟的CatsService。
import { CatsService } from './cats.service';
const mockCatsService = {
/* mock implementation */
};
@Module({
imports: [CatsModule],
providers: [
{
provide: CatsService,
useValue: mockCatsService,
},
],
})
export class AppModule {}
在这个例子中,CatsService标记将解析为mockCatsService模拟对象。
useValue需要一个值 - 在这种情况下,它是一个字面对象,它具有与它替代的CatsService类相同的接口。
由于TypeScript的结构类型,你可以使用任何具有兼容接口的对象,包括字面对象或使用new实例化的类实例。
非基于类的提供程序标记
到目前为止,我们一直使用类名作为我们的提供程序标记(在providers