提供器

提供器是 Nest 中的一个基本概念。许多基本的 Nest 类可以被视为提供器 - 服务、存储库、工厂、助手等等。提供器的主要思想是它可以作为依赖注入;这意味着对象之间可以创建各种关系,并且 “接线” 这些对象的功能很大程度上可以委托给 Nest 运行时系统。

在上一章中,我们搭建了一个简单的 CatsController。控制器应该处理 HTTP 请求并将更复杂的任务委托给提供器。提供程序是在 module 中声明为 providers 的纯 JavaScript 类。

提示 由于 Nest 能够以更加面向对象的方式设计和组织依赖,因此我们强烈建议遵循 SOLID 原则。

服务#

让我们从创建一个简单的 CatsService 开始。该服务将负责数据存储和检索,并设计为供 CatsController 使用,因此将其定义为提供器是一个很好的候选者。

cats.service.ts

JS

1
import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { return this.cats; } }

提示 要使用 CLI 创建服务,只需执行 $ nest g service cats 命令即可。

我们的 CatsService 是一个具有一个属性和两个方法的基本类。唯一的新特性是它使用了 @Injectable() 装饰器。@Injectable() 装饰器附加元数据,该元数据声明 CatsService 是可由 Nest IoC 容器管理的类。顺便说一句,这个例子也使用了一个 Cat 接口,它可能看起来像这样:

interfaces/cat.interface.ts

JS

1
export interface Cat { name: string; age: number; breed: string; }

现在我们有了一个检索猫的服务类,让我们在 CatsController 中使用它:

cats.controller.ts

JS

1
import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; @Controller('cats') export class CatsController { constructor(private catsService: CatsService) {} @Post() async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() async findAll(): Promise<Cat[]> { return this.catsService.findAll(); } }

CatsService 通过类构造函数注入。请注意 private 语法的使用。这种简写允许我们立即在同一位置声明和初始化 catsService 成员。

依赖注入#

Nest 是围绕通常称为依赖注入的强大设计模式构建的。我们建议阅读官方 Angular 文档中有关此概念的精彩文章。

在 Nest 中,由于 TypeScript 的功能,管理依赖非常容易,因为它们只是按类型解析。在下面的示例中,Nest 将通过创建并返回 CatsService 的实例来解析 catsService(或者,在单例的正常情况下,如果已在其他地方请求过,则返回现有实例)。此依赖已解析并传递给控制器的构造函数(或分配给指示的属性):

1
constructor(private catsService: CatsService) {}

作用域#

提供程序通常具有与应用生命周期同步的生命周期 (“scope”)。启动应用时,必须解析每个依赖,因此必须实例化每个提供程序。同样,当应用关闭时,每个提供器都将被销毁。但是,也有一些方法可以使你的提供程序生命周期限定在请求范围内。你可以阅读有关这些技术的更多信息 此处

自定义提供器#

Nest 有一个内置的控制反转 (“IoC”) 容器,可以解决提供器之间的关系。此功能是上述依赖注入功能的基础,但实际上比我们目前所描述的功能强大得多。有几种定义提供器的方法:你可以使用普通值、类以及异步或同步工厂。此处 提供了更多示例。

可选提供器#

有时,你可能有不一定要解决的依赖。例如,你的类可能依赖于配置对象,但如果没有传递任何内容,则应使用默认值。在这种情况下,依赖变为可选,因为缺少配置提供程序不会导致错误。

要指示提供器是可选的,请在构造函数的签名中使用 @Optional() 装饰器。

1
import { Injectable, Optional, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> { constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {} }

请注意,在上面的示例中,我们使用的是自定义提供程序,这就是我们包含 HTTP_OPTIONS 自定义令牌的原因。前面的示例显示了基于构造函数的注入,指示通过构造函数中的类的依赖。阅读有关自定义提供程序及其关联令牌的更多信息 此处

基于属性的注入#

到目前为止,我们使用的技术称为基于构造函数的注入,因为提供器是通过构造函数方法注入的。在某些非常特殊的情况下,基于属性的注入可能很有用。例如,如果你的顶层类依赖于一个或多个提供器,则通过从构造函数在子类中调用 super() 将它们一路向上传递可能会非常乏味。为了避免这种情况,可以在属性级别使用 @Inject() 装饰器。

1
import { Injectable, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> { @Inject('HTTP_OPTIONS') private readonly httpClient: T; }

警告 如果你的类没有扩展另一个类,那么你应该始终更喜欢使用基于构造函数的注入。

提供器注册#

现在我们已经定义了一个提供器 (CatsService),并且我们有了该服务的一个消费者 (CatsController),我们需要向 Nest 注册该服务,以便它可以执行注入。我们通过编辑模块文件 (app.module.ts) 并将服务添加到 @Module() 装饰器的 providers 数组来完成此操作。

app.module.ts

JS

1
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 {}

Nest 现在可以解析 CatsController 类的依赖。

现在我们的目录结构应该是这样的:

src

cats

dto

interfaces

cats.controller.ts

cats.service.ts

app.module.ts

main.ts

手动实例化#

到目前为止,我们已经讨论了 Nest 如何自动处理解决依赖的大部分细节。在某些情况下,你可能需要跳出内置依赖注入系统并手动检索或实例化提供程序。我们在下面简要讨论两个这样的主题。

要获取现有实例或动态实例化提供程序,你可以使用 模块参考

要在 bootstrap() 函数中获取提供程序(例如,对于没有控制器的独立应用,或在引导期间使用配置服务),请参阅 独立应用