NestJS:依赖注入和提供者注册

Hec*_*tor 3 service dependency-injection node.js nestjs

谁能帮助我理解 DI Nest 基础知识,我的问题:

“是否可以有一个没有 @Injectable 注解的服务类,并且该类不属于任何模块?” 我在互联网上看到一个例子,如下所示:

此类存在于公共文件夹中:

export class NotificationService {
  constructor(
    @Inject(Logger) private readonly logger: LoggerService,
    private readonly appConfigService: AppConfigService,
    @Inject(HttpService) private readonly httpService: HttpService
  ) {}
 
  async sendNotification(msg: string) {
   ....
  } 
}
Run Code Online (Sandbox Code Playgroud)

然后它被注册到providers数组中的另一个模块中:

import { Module, Logger, forwardRef, HttpModule } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { NotificationService } from '../../commons/notification/notification.service';
 
@Module({
    imports: [
        ...
    ],
    controllers: [InvoiceController],
    providers: [
        InvoiceService,
        NotificationService,
        Logger],
    exports: [InvoiceService]
})
export class InvoiceModule { }
Run Code Online (Sandbox Code Playgroud)

然后注入到其他服务的构造方法中

@Injectable()
export class InvoiceService {
 
    constructor(
        @Inject(Logger) private readonly logger: LoggerService,
        private readonly notificationService: NotificationService) { }
 
...
}
Run Code Online (Sandbox Code Playgroud)

这工作正常,但我不知道为什么。为什么通知服务没有添加@Injectable,也没有导入模块就可以正确注入?

Jay*_*iel 7

那么让我们来分析一下装饰器到底发生了什么@Injectable()

通常,我们使用装饰器来设置有关我们正在装饰的类、参数、方法或属性的元数据,或者我们使用它通过描述符以某种方式修改方法(如果是方法装饰器)或属性(属性装饰器)。@Injectable()在我们并没有真正做其中任何一个的情况下。当然,我们正在设置范围元数据,但这似乎并没有真正设置任何关于“嘿,这个类可以通过 Nest 框架注入”的元数据。这是因为@Injectable()真正我们设置的是编译器的一个特殊属性tsconfigtscemitDecoratorMetadata属性。有了这个属性,打字稿将在文件的开头和结尾添加一堆额外的函数。这些函数通常看起来像这样

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Run Code Online (Sandbox Code Playgroud)

DelegatorService = __decorate([
    common_1.Injectable(),
    __metadata("design:paramtypes", [http_interceptor_service_1.HttpInterceptorService,
        websocket_interceptor_service_1.WebsocketInterceptorService,
        rpc_interceptor_service_1.RpcInterceptorService,
        gql_interceptor_service_1.GqlInterceptorService])
], DelegatorService);
Run Code Online (Sandbox Code Playgroud)

这是非常重要的部分,因为这"design:paramtypes"实际上是 Nest 在确定注入内容时所读取的内容。

仅当在类中的任何位置使用装饰器时,此元数据才可用,并且实际上有一个关于元数据以及如何破坏它的eslint-typescript 讨论import type,以及最近的 PR最近的一个真正深入了解事物的

我提出所有这些是为了说,因为您@Inject()在构造函数中拥有,所以@Injectable()实际上是无关的,并且没有必要,除非您要设置范围级别元数据。类型元数据已经被发出,所以这解释了为什么你不需要@Injectable()(尽管我仍然认为拥有它是一个好主意,因为它提供了明确的意图)。

现在,为什么注入可以正常工作,我保证这个注入不那么复杂:您将 添加到了的数组NotificationsService中。这告诉 Nest 内部的任何服务都可以访问,因此可以毫无问题地注入到这里。InvoiceModuleprovidersInvoiceModuleNotificationsService

TypeScript 5.0 更新

随着 TypeScript 5.0 的发布,装饰器的行为发生了变化,并且与新提案不emitDecoratorMetadata兼容。新提案目前不允许装饰参数,希望未来的 ECMAScript 功能允许,这就是 NestJS 暂时停留在 TypeScript 4.7 上的原因。