Overview

提供者

学习 NestJS 中的提供者概念,包括服务、依赖注入、作用域和自定义提供者等核心功能。

提供者是 Nest 中的核心概念。许多基本的 Nest 类,如服务、存储库、工厂和助手,都可以被视为提供者。提供者背后的关键思想是它可以作为依赖项被注入,允许对象之间形成各种关系。"连接"这些对象的责任主要由 Nest 运行时系统处理。

在上一章中,我们创建了一个简单的 CatsController。控制器应该处理 HTTP 请求并将更复杂的任务委托给提供者。提供者是在 NestJS 模块中声明为 providers 的普通 JavaScript 类。有关更多详细信息,请参阅"模块"章节。

info 提示 由于 Nest 使您能够以面向对象的方式设计和组织依赖项,我们强烈建议遵循 SOLID 原则

服务

让我们从创建一个简单的 CatsService 开始。此服务将处理数据存储和检索,并将被 CatsController 使用。由于它在管理应用程序逻辑方面的作用,它是被定义为提供者的理想候选者。

@@filename(cats.service)
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;
  }
}
@@switch
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor() {
    this.cats = [];
  }

  create(cat) {
    this.cats.push(cat);
  }

  findAll() {
    return this.cats;
  }
}

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

我们的 CatsService 是一个具有一个属性和两个方法的基本类。这里的关键添加是 @Injectable() 装饰器。此装饰器将元数据附加到类,表明 CatsService 是一个可以由 Nest IoC 容器管理的类。

此外,此示例使用了 Cat 接口,它可能看起来像这样:

@@filename(interfaces/cat.interface)
export interface Cat {
  name: string;
  age: number;
  breed: string;
}

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

@@filename(cats.controller)
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();
  }
}
@@switch
import { Controller, Get, Post, Body, Bind, Dependencies } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
@Dependencies(CatsService)
export class CatsController {
  constructor(catsService) {
    this.catsService = catsService;
  }

  @Post()
  @Bind(Body())
  async create(createCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll() {
    return this.catsService.findAll();
  }
}

CatsService 通过类构造函数注入。注意 private 关键字的使用。这种简写允许我们在同一行中声明和初始化 catsService 成员,简化了过程。

依赖注入

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

在 Nest 中,由于 TypeScript 的功能,管理依赖项很简单,因为它们是根据类型解析的。在下面的示例中,Nest 将通过创建并返回 CatsService 的实例来解析 catsService(或者,在单例的情况下,如果已在其他地方请求过,则返回现有实例)。然后将此依赖项注入到控制器的构造函数中(或分配给指定的属性):

constructor(private catsService: CatsService) {}

作用域

提供者通常具有与应用程序生命周期一致的生命周期("作用域")。当应用程序启动时,每个依赖项都必须被解析,这意味着每个提供者都会被实例化。同样,当应用程序关闭时,所有提供者都会被销毁。但是,也可以使提供者成为请求作用域,这意味着其生命周期与特定请求相关联,而不是与应用程序的生命周期相关联。您可以在注入作用域章节中了解更多关于这些技术的信息。

自定义提供者

Nest 带有一个内置的控制反转("IoC")容器,用于管理提供者之间的关系。此功能是依赖注入的基础,但实际上比我们迄今为止涵盖的内容更强大。有几种定义提供者的方法:您可以使用普通值、类以及异步或同步工厂。有关定义提供者的更多示例,请查看依赖注入章节。

可选提供者

有时,您可能有不总是需要解析的依赖项。例如,您的类可能依赖于配置对象,但如果没有提供,则应使用默认值。在这种情况下,依赖项被认为是可选的,配置提供者的缺失不应导致错误。

要将提供者标记为可选,请在构造函数的签名中使用 @Optional() 装饰器。

import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

In the example above, we're using a custom provider, which is why we include the HTTP_OPTIONS custom token. Previous examples demonstrated constructor-based injection, where a dependency is indicated through a class in the constructor. For more details on custom providers and how their associated tokens work, check out the Custom Providers chapter.

基于属性的注入

到目前为止我们使用的技术称为基于构造函数的注入,提供者通过构造函数方法注入。在某些特定情况下,基于属性的注入可能很有用。例如,如果您的顶级类依赖于一个或多个提供者,通过在子类中调用 super() 将它们一路传递可能会变得繁琐。为了避免这种情况,您可以直接在属性级别使用 @Inject() 装饰器。

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

warning 警告 如果您的类不扩展另一个类,通常最好使用基于构造函数的注入。构造函数清楚地指定了需要哪些依赖项,与使用 @Inject 注释的类属性相比,提供了更好的可见性并使代码更易于理解。

提供者注册

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

@@filename(app.module)
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/
│   │   └── create-cat.dto.ts
│   ├── interfaces/
│   │   └── cat.interface.ts
│   ├── cats.controller.ts
│   └── cats.service.ts
├── app.module.ts
└── main.ts

手动实例化

到目前为止,我们已经介绍了 Nest 如何自动处理解析依赖项的大部分细节。但是,在某些情况下,您可能需要跳出内置的依赖注入系统并手动检索或实例化提供者。下面简要讨论两种这样的技术。

  • 要检索现有实例或动态实例化提供者,您可以使用模块引用
  • 要在 bootstrap() 函数中获取提供者(例如,对于独立应用程序或在引导期间使用配置服务),请查看独立应用程序