前言
文章看着长主要是代码占地多...
最主要的是虽然代码贴的多,但是大部分代码没怎么改,几乎所有都是只改了Provider .....
所以大家不要害怕,这个比前面两章都简单~~
这是先修,要先有以下这3篇的概念才能看懂哦~~
依赖注入 (Dependency Injection)
依赖注入是一种设计方法,透过此方式可以大幅降低耦合度。
依赖注入跟 Provider 还有 Module 有什么关系呢?仔细回想一下,当我们在 Controller 的 constructor
注入了 Service 后,没有使用到任何 new
却可以直接使用。这里以 app.controller.ts
为例:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }
@Get()
getHello(): string {
return this.appService.getHello();
}
}
没有经过实例化那这个实例从哪里来的?前面有说过当 Module 建立起来的同时,会把 providers
里面的项目实例化,而我们注入的 Service 就是透过这样的方式建立实例的
,也就是说有个机制在维护这些实例,这个机制叫 控制反转容器 (IoC Container) 。
控制反转容器是透过 透过
token
来找出对应项目的,有点类似key/value
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService //写了一个 `AppService` 就指定了`token`
{ provide: AppService, useClass: AppService }可以看到变成了一个物件,
//该物件的 provide 即 tokenuseClass 则是指定使用的 class 为何,进而建立实例。
],
})
export class AppModule { }
provide原理{ provide: AppService, useClass: AppService }可以看到变成了一个物件, 该物件的 provide 即 tokenuseClass 则是指定使用的 class 为何,进而建立实例。
Provider
Provider 透过控制反转容器做实例的管理,可以很方便且有效地使用这些 Provider,而 Provider 大致上可以分成两种:
1. 标准Provider
在 class
上添加*@Injectable
*让 Nest 知道这个 是可以由控制反转容器管理的,透过 Nest 建立
nest generate service <SERVICE_NAME>
2.自定义 Provider
如果觉得标准 Provider 无法满足需求,如:
- 想自行建立一个实例,而不是透过 Nest 建立。
- 想要在其他依赖项目中重用实例。
- 使用模拟版本的
class
进行覆写,以便做测试 Nest 提供了多种方式来自订 Provider,都是透过展开式进行定义:
Value Provider
这类型的 Provider 主要是用来:
- 提供常数 (Constant)。
- 将外部函式库注入到控制反转容器。
- 将
class
抽换成特定的模拟版本。 那要如何使用呢?在展开式中使用useValue
来配置。这里以app.module.ts
为例:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: AppService,
useValue: {
name: 'HAO'
}
}
],
})
export class AppModule { }
修改 app.controller.ts
来查看 token
为 AppService
的内容为何:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {
console.log(this.appService);
}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
会发现注入的 AppService
变成我们指定的物件,会在终端机看到结果:
{ name: 'HAO' }
没关系,Nest 提供了多种方式来自订 Provider,都是透过展开式进行定义:
非类别型 token
事实上,Provider 的 token
不一定要使用class
,Nest 允许使用以下项目:
string
symbol
enum
这边同样以 app.module.ts
为例,我们指定 token
为字串HANDSOME_MAN
,并使用 HAO
作为值:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: 'HANDSOME_MAN',
useValue: 'HAO'
}
],
})
export class AppModule { }
在注入的部分需要特别留意,要使用 @Inject(token?: string)
装饰器来取得。这里以 app.controller.ts
为例:
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
@Inject('HANDSOME_MAN') private readonly handsome_man: string
) {
console.log(this.handsome_man);
}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
会发现注入的 HANDSOME_MAN
即为指定的值,在终端机会看到:
HAO
提醒:通常会把这类型的
token
名称放在独立的档案里,好处是当有其他地方需要使用的时候,可以直接取用该档案里的内容,而不需要再重写一次token
的名称。
导出自定义provider
我们把 Custom Provider 的 展开式用变数储存起来,再将该展开式放到 providers
与 exports
中:
import { Module } from '@nestjs/common';
const HANDSOME_HAO = {
provide: 'HANDSOME_MAN',
useValue: {
name: 'HAO'
}
};
@Module({
providers: [
HANDSOME_HAO
],
exports: [
HANDSOME_HAO
]
})
export class HandsomeModule { }
在 AppModule
进行汇入,下方为 app.module.ts
的范例:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HandsomeModule } from './handsome/handsome.module';
@Module({
imports: [HandsomeModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
修改 app.controller.ts
查看结果,会在终端机看到:
{ name: 'HAO' }
非同步 Provider
有时候可能需要等待某些非同步的操作来建立 Provider,比如:需要与资料库连线,Nest App 会等待该 Provider 建立完成才正式启动。这边以上方的范例来做修改,调整一下 handsome.module.ts
的内容:
import { Module } from '@nestjs/common';
const HANDSOME_HAO = {
provide: 'HANDSOME_MAN',
useFactory: async () => {
const getHAO = new Promise(resolve => {
setTimeout(() => resolve({ name: 'HAO' }), 2000);
});
const HAO = await getHAO;
return HAO;
}
};
@Module({
providers: [
HANDSOME_HAO
],
exports: [
HANDSOME_HAO
]
})
export class HandsomeModule { }
在等待两秒后,终端机会出现下方结果:
{ name: 'HAO' }
自选式 Provider
有时候可能会有 Provider 没有被提供但却注入的情况,这样在启动时会报错,因为 Nest 找不到对应的 Provider,那遇到这种情况该如何处理呢?首先,遇到这类型情况通常会给个预设值代替没被注入的 Provider,然后要在注入的地方添加 @Optional
装饰器。这里我们同样沿用上方范例,去修改 app.module.ts
的内容,将 HandsomeModule
移除汇入:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
接着去修改 app.controller.ts
的内容,替 HANDSOME_MAN
给定预设值:
import { Controller, Get, Inject, Optional } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
@Optional() @Inject('HANDSOME_MAN') private readonly handsomeMan = { name: '' }
) {
console.log(this.handsomeMan);
}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
此时的终端机会显示结果:
{ name: '' }
3.Class Provider
这类型的 Provider 最典型的用法就是让 token
指定为抽象类别,并使用 useClass
来根据不同环境提供不同的实作类别。这里以 app.module.ts
为例:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodoModule } from './features/todo/todo.module';
import { TodoService } from './features/todo/todo.service';
class HandSomeMan {
name = 'HAO';
}
class TestHandSomeMan {
name = 'HAO';
}
@Module({
imports: [TodoModule],
controllers: [AppController],
providers: [
AppService,
{
provide: TodoService,
useClass: process.env.NODE_ENV === 'production' ? HandSomeMan : TestHandSomeMan
}
],
})
export class AppModule { }
稍微改写一下app.controller.ts
:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { TodoService } from './features/todo/todo.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly todoService: TodoService
) {
console.log(this.todoService);
}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
如果环境变数 NODE_ENV
不等于 production
的话,会在终端机看到下方结果:
TestHandSomeMan { name: 'HAO' }
4.Factory Provider
这类型的 Provider 使用工厂模式让 Provider 更加灵活,透过 注入其他依赖 来变化出不同的实例,是很重要的功能。使用** useFactory 来指定工厂模式的函数**,并透过** inject 来注入其他依赖**。以 app.module.ts 为例:
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: 'MESSAGE_BOX',
useFactory: (appService: AppService) => {
const message = appService.getHello();
return new MessageBox(message);
},
inject: [AppService]
}
],
})
export class AppModule { }
稍微修改一下app.controller.ts
:
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
@Inject('MESSAGE_BOX') private readonly messageBox
) {
console.log(this.messageBox);
}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
会在终端机看到下方结果:
MessageBox { message: 'Hello World!' }
小结
- Provider 与 Module 之间有依赖注入机制的关系。透过控制反转容器管理 Provider 实例。
- 有四种方式提供自定义 Provider:
useValue
、useClass
、useFactory
、useExist
。 - Provider 的
token
可以是:string
、symbol
、enum
。
Provider 小技巧:
- 自定义 Provider 可以透过把展开式抽离至变数来进行汇出。
- Provider支援非同步 建立,在尚未建立完 Provider 以前,Nest App 不会正式启动。
- 如果 Provider 并不是必须项目,必须在注入的地方添加
@Optional
,这边也建议替该参数设置预设值。
下期讲Exception & Exception filters,下期见啦~~