NestJS01:providers自定义、动态生成

2023年 7月 14日 27.3k 0

前言

最近在学nestjs,涉及基础概念的东西记录一下,顺便分享下来;大部分内容来自于nestjs官网,加上自己的实践和部分理解,希望对大家有帮助。

话不多说,直接开整!

一、providers是什么、怎么用

一般node后端的项目结构会这么划分几个文件夹,如controllerservicerouter

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.tscat.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.tscat.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类型

image.png

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的基本使用,自定义用法,还有一些代码类型注释;

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论