前言
最近在学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的具体内容;举个场景,我想根据项目配置,来返回AService或者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<T = any> = Type<any> | ClassProvider<T> | ValueProvider<T> | FactoryProvider<T> | ExistingProvider<T>; // 对应我们的类构造器 export interface Type<T = any> extends Function { new (...args: any[]): T; } export interface ClassProvider<T = any> { provide: InjectionToken; useClass: Type<T>; // 对应复用实例 or 每次都创建一个新的实例 or 等请求进来再创建,默认是app启动复用创建 scope?: Scope; inject?: never; // 是否惰性生成实例 durable?: boolean; } export interface ValueProvider<T = any> { provide: InjectionToken; useValue: T; inject?: never; } export interface FactoryProvider<T = any> { provide: InjectionToken; useFactory: (...args: any[]) => T | Promise<T>; inject?: Array<InjectionToken | OptionalFactoryDependency>; scope?: Scope; durable?: boolean; } export interface ExistingProvider<T = any> { provide: InjectionToken; useExisting: any; }
小结
这一章主要记录了nestjs的provider的基本使用,自定义用法,还有一些代码类型注释;
发表评论 取消回复