Nest 实现了 IoC 容器,会从入口模块开始扫描,分析 Module 之间的引用关系,对象之间的依赖关系,自动把 provider 注入到目标对象。
而这个 provider 也有好几种,这节我们就来看一下。
我们创建个 nest 项目:
nest new custom-provider
可以看到 AppService 是被 @Injectable 修饰的 class:
在 Module 的 providers 里声明:
这就是 provider。
其实这是一种简写,完整的写法是这样的:
{
provide: AppService,
useClass: AppService
}
通过 provide 指定 token,通过 useClass 指定对象的类,Nest 会自动对它做实例化后用来注入。
在 AppController 的构造器里参数里声明了 AppService 的依赖,就会自动注入:
如果不想用构造器注入,也可以属性注入:
@Inject(AppService)
private readonly appService: AppService;
通过 @Inject 指定注入的 provider 的 token 即可。
有的同学说,在构造器参数里指定 AppService 的依赖的时候也没指定 token 啊?
那是因为 AppService 这个 class 本身就是 token。
当然,这个 token 也可以是字符串:
{
provide: 'app_service',
useClass: AppService
}
如果 token 是字符串的话,注入的时候就要用 @Inject 手动指定注入对象的 token 了:
@Inject('app_service') private readonly appService: AppService
我们调试下:
点击调试面板的 create launch.json file,创建调试配置文件:
添加这样一个调试配置:
{
"type": "node",
"request": "launch",
"name": "debug nest",
"runtimeExecutable": "npm",
"args": [
"run",
"start:dev",
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
}
在 getHello 方法打个断点,点击调试启动:
浏览器访问 http://localhost:3000 ,代码会在断点处断住。
可以看到,这时候 appService 就有值了。
也就是说,用字符串或者 class 做 token 的 provider,都可以正确被注入到目标对象。
相比之下,用 class 做 token 可以省去 @Inject,比较简便。
除了指定 class 外,还可以直接指定一个值,让 IoC 容器来注入。
{
provide: 'person',
useValue: {
name: 'aaa',
age: 20
}
}
使用 provide 指定 token,使用 useValue 指定值。
然后在对象里注入它:
@Inject('person') private readonly person: {name: string, age: number}
调试一下可以看到,确实是注入了:
provider 的值可能是动态产生的,Nest 也同样支持:
{
provide: 'person2',
useFactory() {
return {
name: 'bbb',
desc: 'cccc'
}
}
}
我们可以使用 useFactory 来动态创建一个对象。
在对象里注入:
@Inject('person2') private readonly person2: {name: string, desc: string}
类型是 {name: string, age: number} 。
调试下,也是可以拿到创建出的对象的:
这个 useFactory 支持通过参数注入别的 provider:
{
provide: 'person3',
useFactory(person: { name: string }, appService: AppService) {
return {
name: person.name,
desc: appService.getHello()
}
},
inject: ['person', AppService]
}
通过 inject 声明了两个 token,一个是字符串 token 的 person,一个是 class token 的 AppService。
也就是注入这两个 provider:
在 return 那里打个断点。
可以看到,在调用 useFactory 方法的时候,Nest 就会注入这两个对象:
useFactory 支持异步:
{
provide: 'person5',
async useFactory() {
await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
return {
name: 'bbb',
desc: 'cccc'
}
},
},
Nest 会等拿到异步方法的结果之后再注入:
这样就可以更灵活的创建注入对象。
此外,provider 还可以通过 useExisting 来指定别名:
{
provide: 'person4',
useExisting: 'person2'
}
这里就是给 person2 的 token 的 provider 起个新的 token 叫做 person4。
然后就可以用新 token 来注入了:
这些自定义 provider 的方式里,最常用的是 useClass,不过我们一般会用简写,也就是直接指定 class。
useClass 的方式由 IoC 容器负责实例化,我们也可以用 useValue、useFactory 直接指定对象。
useExisting 只是用来起别名的,有的场景下会用到。
比如 @nestjs/typeorm 里就用到了 useValue、useFactory、useExisting:
它用 useValue 来注入一段字符串:
用 useFactory 根据传入的 options 动态创建数据库连接对象:
用 useExisting 给 DataSource 起了一个 Connection 的别名:
这里是一个版本用了 Connection,一个版本用了 DataSource,通过 useExisting 起别名就可以兼容两者。
此外,如果觉得构造器注入写起来不方便,可以使用属性注入,效果一样:
案例代码在小册仓库。
总结
一般情况下,provider 是通过 @Injectable 声明,然后在 @Module 的 providers 数组里注册的 class。
默认的 token 就是 class,这样不用使用 @Inject 来指定注入的 token。
但也可以用字符串类型的 token,不过注入的时候要用 @Inject 单独指定。
除了可以用 useClass 指定注入的 class,还可以用 useValue 直接指定注入的对象。
如果想动态生成对象,可以使用 useFactory,它的参数也注入 IOC 容器中的对象,然后动态返回 provider 的对象。
如果想起别名,可以用 useExisting 给已有的 token,指定一个新 token。
灵活运用这些 provider 类型,就可以利用 Nest 的 IOC 容器中注入任何对象。