前言

在nestjs中,我们在controller或者在service中去调用一些依赖,不是像写vue那种,直接es6导入导出,而是通过依赖注入的形式,在类构造函数constructor中声明需要的依赖,nestjs会在实例化的时候将对应的依赖实例作为参数传入

示例:

app.controller.ts

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";

@Controller()
export class AppController {
    constructor(private appService: AppService) {}

    @Get()
    getHello(): string {
        return this.appService.getHello();
    }
}

controller中我们需要获取到AppService服务(依赖),于是在constructor中声明了一个私有属性appService,它的类型是AppService

getHello函数中我们就可以直接使用这个依赖。

我们代码中并没有明确实例化的代码,这个过程则是由nestjs为我们实现的。

首先我们来了解下什么是依赖注入:

什么是依赖注入?

依赖注入是一种设计模式:Dependency Injection;简称DI。

它的核心思想是:将组件所依赖的其他组件的实例提供给它,而不是让它自己去创建这些依赖,这样组件不需要关心如何创建和管理它所依赖的组件,只需要调用即可。

这里我们来举个例子,以面向对象的思维去想(别想函数式编程了),比如说要创建一个网页,这个网页会有三个功能,分别是header、body、footer,他们分别承载的不同效果,header负责导航,body负责内容展示,footer负责版权和其他内容展示。

从低心智的角度编程,大概率会这么写:

import Header from "./header";
import Body from "./body";
import Footer from "./footer";

class Page {
  constructor(headerConfig, bodyConfig, footerConfig) {
    const header = new Header(headerConfig);

    const body = new Body(bodyConfig);

    const footer = new Footer(footerConfig);
  }
}

const page = new Page(
  {
    el: "header",
    data: {
      title: "我是标题",
    },
  },
  {
    el: ".main",
    data: {
      content: "我是内容",
    },
  },
  {
    el: "footer",
    data: {
      content: "我是底部",
    },
  }
);

当我实例化这个page类的时候,我需要将header、body、footer的参数作为page类的参数传入,因为page负责了实例化它们。

如果我们再增加更多的依赖,那page的参数维护简直就是噩梦级别,从解耦的角度去看,为什么page要负责实例化他们?它明明要做的事情是基于业务去调用对应的依赖的功能,而不是花大把的功夫去维护实例化的成本。

所以这里我们采用了一种设计模式:控制反转-依赖注入。

将实例化的操作转移到外部,page类通过函数参数的形式接受依赖实例,这个就好比组装电脑了,你组个电脑难道要自己造个cpu嘛,没必要啊,是吧,我买个现成的成品自己组装下就行了。

而依赖注入也没有那么难懂,可能名字挺高大上的,说白了就是传参,以及如何去传参。

依赖注入的三种方式

依赖注入可以通过三种方式实现:

  1. 构造函数注入(Constructor Injection):通过构造函数将依赖传递给组件。组件在创建时,需要提供依赖的实例作为构造函数的参数。
  2. 属性注入(Property Injection):通过公开的属性将依赖注入给组件。依赖的实例通过属性赋值给组件。
  3. 方法注入(Method Injection):通过调用方法将依赖注入给组件。组件在需要依赖时,调用特定的方法,并将依赖作为参数传递进去。

而nestjs用的就是构造函数注入方式。

依赖注入demo

import Header from "./header";
import Body from "./body";
import Footer from "./footer";

const header = new Header({
  el: "header",
  data: {
    title: "我是标题",
  },
});

const body = new Body({
  el: ".main",
  data: {
    content: "我是内容",
  },
});

const footer = new Footer({
  el: "footer",
  data: {
    content: "我是底部",
  },
});

class Page {
  constructor(header, body, footer) {
    this.header = header;
    this.body = body;
    this.footer = footer;
  }
}

const page = new Page(header, body, footer);

nestjs如何实现依赖注入的?

首先我们先看模块的声明:

app.module.ts

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";

@Module({
    imports: [],
    controllers: [AppController],
    providers: [AppService]
})
export class AppModule {}

可以看到AppModule模块使用了一个类装饰器@Module,它接收了三个参数,其中providers就是这个模块需要使用到的依赖,现在使用的是简写形式,我们转成完整形式如下:

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";

@Module({
    imports: [],
    controllers: [AppController],
    providers: [
      {
        provide: AppService,
        useClass: AppService
      }
    ]
})
export class AppModule {}

可以看到它其实是一个对象,其中provide的值可以理解为key,而useClass表示的是值,当然值有很多种,所以除了useClass还有useValue表示普通对象等等的属性,具体可以看文档了。

好了,现在我们明白在模块上通过类装饰器其实已经明确声明了所需要的依赖了,有key有value。

那么nestjs是如何知道对应的controller或者在service中需要这个依赖呢?

这里我们就需要了解一下ts和元数据了。

nestjs通过什么方式知道需要什么样的依赖的?

我们来看个例子:

function ParameterType(target: any, methodName: string, parameterIndex: number) {
  const paramType = Reflect.getMetadata('design:paramtypes', target, methodName)[parameterIndex];
  console.log(`Parameter type: ${paramType.name}`);
}

class MyClass {
  myMethod(@ParameterType param: string) {
    // ...
  }
}

const instance = new MyClass();
instance.myMethod('Hello');

在ts中我们可以通过'design:paramtypes'预设的key来获取元数据存储的构造函数参数类型。

所以上面我们可以得到一个Parameter type: String的打印值,其中String就是类型。

然后我们再回到app.controller.ts来打印一下:

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";

@Controller()
export class AppController {
    constructor(private appService: AppService) {}

    @Get()
    getHello(): string {
        return this.appService.getHello();
    }
}

console.log("🚀 ~ file: app.module.ts:4 ~ AppService:", Reflect.getMetadata("design:paramtypes", AppController));

然后我们可以得到:

🚀 ~ file: app.module.ts:4 ~ AppService: [ [class AppService] ]

可以看到我们得到了这个类型AppService,这个就是key了,然后nestjs会去模块的providers中通过key去匹配到对应的值,如果是一个class,就会将其实例化,如果已经实例化了,就直接取实例化后的值(单例形式)。

这里我们就可以初步了解到nestjs的依赖注入是怎么实现了,关键点就在于key是怎么配置的,怎么获取的。

官方自己实现了一个IoC的类,专门处理这个实例化。
IoC 中文名就是控制反转,DI 依赖注入则是实现的形式之一。

如果是一个普通对象,可能就没法像class一样,用class对象来作为key,那么我们需要使用参数构造器@Inject('key')的形式。

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

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

结束

至此初级了解已经完成,以后有机会可以看看完整的实现,我看很多课程会去刻意仿nestjs的依赖注入实现。

分类: Nest.js 标签: 装饰器Nestjs依赖注入控制反转元数据'design:paramtypes'

评论

暂无评论数据

暂无评论数据

目录