前言
最近在学nestjs,涉及基础概念的东西记录一下,顺便分享下来;大部分内容来自于nestjs官网,加上自己的实践和部分理解,希望对大家有帮助。
话不多说,直接开整!
一、providers是什么、怎么用
一般node后端的项目结构会这么划分几个文件夹,如controller
、service
、router
;
constroller
:对应路由的处理器,比如定义遇到了/user的get请求,我将请求分发给谁处理;这里的谁通常指的是service
兄弟,那可以直接把逻辑写在controller里面吗,可以!但不建议;
service
:字面意思服务,一般对应各种单独的业务服务,比如用户相关的作为一个service,里面获取所有用户信息作为service的一个方法,比如前面的/user获取所有用户信息,要调用对应的service.getAllUser()
router
:设置请求路径指向哪个控制器
回到文章,我们的providers正对应中间的service
,每一个provider通常对应一个service作为服务的提供者;当然provider也不一定就是service
,也可以是一个函数,是一个对象;
在nestjs中,定义一个provider用@Injectable装饰器
,如下代码,此时我的CatService
就是一个provider,你看形式,是不是很像service;
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatService {
findAll() {
return [{ name: 'cat service' }];
}
}
二、标准的provider
我们先创建项目,在终端nest new project-name
,然后cd project-name
,最后运行yarn start:dev
;通过nest-cli创建的项目,会帮我们生成好app模块;然后我们在src
目录下里面新建cat.controller.ts
、cat.service.ts
,代码如下
cat.controller.ts文件如下
import { Controller, Get } from '@nestjs/common';
import { CatService } from './cat.service';
@Controller('/cat')
export class CatController {
constructor(private readonly catService: CatService) {}
@Get()
getAll() {
return this.catService.findAll();
}
}
提一嘴
,构造器那里是ts的一个语法糖,具体参考这个例子
class SampleClass {
private foo: string
constructor(_foo: string) {
this.foo = _foo;
}
}
class SampleClass {
constructor(private foo: string) {}
}
cat.service.ts如下
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatService {
findAll() {
return [{ name: 'cat service' }];
}
}
最后在app模块里面的controller和providers引用我们的cat控制器和provider
,然后启动项目,访问http://localhost:3000/cat 就能看到浏览器正常输出了
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatModule } from './cat/cat.module';
@Module({
imports: [],
controllers: [AppController, CatController],
providers: [AppService, CatService],
})
export class AppModule {}
这样是可以了,但是如果来cat2、cat3、cat4、每个都这么引用,是不是不太好,所以再改进,我们使用@Module维护功能类似的controller、provider作为一个模块,然后引用整个模块;
新建一个文件夹cat
,将我们上面创建的cat.controller.ts
、cat.service.ts
放进cat文件夹中,然后新建cat.module.ts
并写入下面代码
cat.module.ts
import { Injectable, Module } from '@nestjs/common';
import { CatController } from './cat.controller';
import { CatService } from './cat.service';
@Module({
controllers: [CatController],
providers: [CatService],
})
export class CatModule {}
最后修改app.module.ts的代码,引用我们的cat模块;重新启动项目后,发现效果是一样的,但代码的可维护性变高了!
@Module({
imports: [CatModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
三、自定义provider注入和使用
自定义provider的几种方式先列一下,主要下面几种
- useClass:
@Injectable
修饰的class - useValue:直接使用一个我们定义好的变量
- useFactory:
好用!
,可以动态返回provider的具体内容 - useExisting:别名
复用
已经存在的provider
要想自定义provider,首先我们要换一下Module里面的providers注入写法,由原本的直接CatService,变成一个对象;provide类似一个标识,可以填类,也可以填写字符串;
useClass
表示使用哪个service;如果是其他Service,要先注册到privders数组中,
@Module({
// providers: [CatService]
providers: [
{
provide: CatService,
useClass: CatService
}
]
})
provide为字符串的情况
,要修改controller的注入方式
@Module({
// providers: [CatService],
providers: [
{
provide: "nest-cat",
useClass: CatService
}
]
})
// cat.controller.ts文件修改如下
import { Controller, Get, Inject } from '@nestjs/common';
import { CatService } from './cat.service';
@Controller('/cat')
export class CatController {
constructor(@Inject('nest-cat') private readonly catService: CatService) {}
@Get()
getAll() {
return this.catService.findAll();
}
}
字符串和类的的区别:
如果是字符串,nestjs这时候并不知道catService对应哪个具体值,需要提前手动用@Inject('token')注入,表示用哪个provide;
如果是类的话,则直接声明catService的类型是CatService类;ts会根据构造器函数的类型,进行依赖注入(relect metadata)
- 字符串首先需要手动@Inject('对应的表示标识'),
- 类需要声明对应的类型
useValue
,返回任意一个值;比如你嫌弃还要写一个catService,你就直接返回一个对象,包含findAll方法,然后给controller用;也是可以的
{
provide: CatService,
useValue: {
findAll() {
return [{ name: 'use value' }]
}
}
}
useFactory
这个就更好用了,可以动态返回provider的具体内容;举个场景,我想根据项目配置,来返回A
Service或者B
Service;inject表示的useFactory需要注入的依赖,按顺序放在函数的参数位置上;
ps:我在我具体的项目用的是,封装一个Redis模块,然后等待连接,返回redis连接实例;
TomCatService,
{
provide: CatService,
inject: [TomCatService],
useFactory: async (tomCatService: TomCatService) => {
return tomCatService;
// return {
// findAll: () => {
// return [{ name: 'use factory' }];
// },
// };
},
},
// 使用useClass也可以完成类似效果
{
provide: CatService,
useClass: process.env.NODE_ENV === 'development' ? CatService : TomCatService,
},
或者异步请求其他数据,然后再返回其他service,提供服务;试下面的例子,会发现每个请求大概要等3s;
{
provide: CatService,
// 这里的意思是,每个请求创建不同实例,进来一个等待3s
scope: Scope.REQUEST,
useFactory: async () => {
await new Promise((resolve) => setTimeout(resolve, 3000));
return new CatService();
},
},
useExisting
:别名注入依赖,用的不多,简单看下用法
{
provide: CatService,
useClass: CatService,
},
{
// 定义表示,然后在controller那里注入
provide: 'AliasCatService',
useExisting: CatService,
},
// cat.controller.ts
@Controller('/cat')
export class CatController {
constructor(
@Inject('AliasCatService') private readonly catService: CatService,
) {}
@Get()
getAll() {
return this.catService.findAll();
}
}
其他补充
:如果用到了其他的provider,需要在当前模块里面的providers先引入,在进行使用;
NestJs的Provider类型
export type Provider = Type | ClassProvider | ValueProvider | FactoryProvider | ExistingProvider;
// 对应我们的类构造器
export interface Type extends Function {
new (...args: any[]): T;
}
export interface ClassProvider {
provide: InjectionToken;
useClass: Type;
// 对应复用实例 or 每次都创建一个新的实例 or 等请求进来再创建,默认是app启动复用创建
scope?: Scope;
inject?: never;
// 是否惰性生成实例
durable?: boolean;
}
export interface ValueProvider {
provide: InjectionToken;
useValue: T;
inject?: never;
}
export interface FactoryProvider {
provide: InjectionToken;
useFactory: (...args: any[]) => T | Promise;
inject?: Array;
scope?: Scope;
durable?: boolean;
}
export interface ExistingProvider {
provide: InjectionToken;
useExisting: any;
}
小结
这一章主要记录了nestjs的provider的基本使用,自定义用法,还有一些代码类型注释;