likes
comments
collection
share

Nest.js 从零到壹详细系列(三):模块ModuleNest 用模块来组织应用程序结构,可以理解为前端的组件。Nes

作者站长头像
站长
· 阅读数 46

1. 基本概念

模块是具有 @Module() 装饰器的类。 Nest 用模块来组织应用程序结构,可以理解为前端的组件。Nest的模块与前端的组件,都是具有一定的封装性和可复用性。

@Module() 装饰器共接受四个属性:

属性说明
providers指定当前模块中的提供者(可在当前模块中共享)
controllers指定当前模块中的控制器
imports指定当前模块依赖的其他模块
exports指定当前模块中哪些服务或其他提供者可以被其他模块使用

2. 基本使用

使用脚手架搭建的nest项目,都会有个app.module.ts文件

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

使用controllers注册了AppController控制器,使用providers注册了AppService服务。

3. 模块导入导出

模块除了使用自己的Service服务,还可以使用其他模块的Service服务。只需要一个模块使用exports导出自身的服务,另一个模块使用imports导入其他模块提供的服务。

这里举个示例,生成auth和user两个模块,auth服务调用user服务中的方法。

1. 生成user模块

//生成module
nest g mo user

//生成service 
nest g s user --no-spec

2. 在user.service.ts中写个findUser方法

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  findUser() {
    //...查找用户相关业务逻辑
    return true;
  }
}

3. 修改user.module.ts,将 UserService 导出

import { Module } from '@nestjs/common';
import { UserService } from './user.service';

@Module({
  providers: [UserService],
  //导出UserService
  exports: [UserService],
})
export class UserModule {}

4. 生成auth模块

//生成module
nest g mo auth

//生成service
nest g s auth --no-spec

//生成service
nest g co auth --no-spec

5. 修改auth.module.ts,在AuthModule中导入UserModule

import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UserModule } from '../user/user.module';

@Module({
  imports: [UserModule], //引入UserModule
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

6. 修改auth.service.ts,使用UserService

import { Injectable } from '@nestjs/common';
import { UserService } from '../user/user.service';

@Injectable()
export class AuthService {
  constructor(private readonly userService: UserService) {}

  validateUser() {
    if (this.userService.findUser()) {
      return '找到用户';
    } else {
      return '未找到用户';
    }
  }
}

提示:这里引用UserService只是为了做类型提示

7. 修改auth.controller.ts,在AuthController定义路由

import { Controller, Get } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Get()
  getUser() {
    return this.authService.validateUser();
  }
}

使用Postman访问Get请求localhost:3000/auth,得到“找到用户”

4. 全局模块

@Global 装饰器使模块成为全局作用域。全局模块只需注册一次,一般由根模块app.module.ts注册。

还是拿上述auth和user两个模块举例,不过这里将user声明为全局模块

1. 修改user.module.ts

import { Module, Global } from '@nestjs/common';
import { UserService } from './user.service';

@Global() // 申明全局模块
@Module({
  providers: [UserService],
  //导出UserService
  exports: [UserService],
})
export class UserModule {}

2. 在app.module.ts中注册全局user模块

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [UserModule, AuthModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

3. 修改auth.module.ts,不再需要引入user模块

import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';

@Module({
  imports: [],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

使用Postman访问Get请求localhost:3000/auth,得到“找到用户”

5. 动态模块

动态模块是指那些在运行时根据配置,动态创建的模块。例如根据传参读取不同的文件。

1. 基本示例

1. 在src目录下新建env文件夹,并新建.env文件

DB_HOST=127.0.0.1  

2. 在src目录下新建common文件夹,并新建constants.ts文件

export const CONFIG_OPTIONS_ENV = 'CONFIG_OPTIONS_ENV';

3. 生成config模块

//生成module
nest g mo config

//生成service
nest g s config --no-spec

//生成service
nest g co config --no-spec

4. 修改config.module.ts,定义动态模块

import { Module, DynamicModule } from '@nestjs/common';
import { CONFIG_OPTIONS_ENV } from '../common/constants';
import { ConfigService } from './config.service';
import { ConfigController } from './config.controller';

@Module({
  controllers: [ConfigController],
})
export class ConfigModule {
  static register(options): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        { provide: CONFIG_OPTIONS_ENV, useValue: options }, // 自定义提供者
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

在ConfigModule 类中,定义一个 register 静态方法。register 方法接受参数options,并返回一个类型为 DynamicModule 的对象。

register 的返回值是一个对象,其对象中必须包含 module 属性,其值为 module 类名,其他属性与静态模块保持一致

providers:是一个提供者数组,定义了哪些服务可以在当前模块内提供。这里有两个提供者:第一个提供者使用 provide 定义提供的字段名,使用 useValue 的定义提供的值,以便在 ConfigService 中使用。第二个提供者引用了 ConfigService 类。

熟悉Vue的读者,可能会联想到跨组件传值的provide和inject。在Nest中也类似,使用provide提供值,然后在service中使用inject使用值。

5. 修改config.service.ts,使用inject获取module中提供的值

import { Injectable, Inject } from '@nestjs/common';
import * as path from 'path';
import * as fs from 'fs';
import * as dotenv from 'dotenv';
import { CONFIG_OPTIONS_ENV } from '../common/constants';

@Injectable()
export class ConfigService {
  private readonly envConfig;

  constructor(
    @Inject(CONFIG_OPTIONS_ENV)
    private configOptions,
  ) {
    /**
     * 组装完整路径
     * 读取配置文件信息
     */
    const envFile = path.resolve(__dirname, configOptions.folder);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  //获取配置信息
  getAll() {
    return this.envConfig;
  }
}

构造函数接收一个通过 @Inject(CONFIG_OPTIONS_ENV) 注入的 configOptions 参数,即外部传递的参数。

构造函数中,即对路径进行拼接,并读取文件内容,最后使用dotenv.parse解析文件内容为键值对对象。

6. 修改config.controller.ts,定义一个简单的控制器

import { Controller, Get } from '@nestjs/common';
import { ConfigService } from './config.service';

@Controller('config')
export class ConfigController {
  constructor(private readonly configService: ConfigService) {}
  @Get()
  getAllConfig() {
    return this.configService.getAll();
  }
}

7. 在app.module.ts中,引用动态模块ConfigModule,并传递参数

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule.register({ folder: '../../src/env/.env' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

在 app.module.ts 中导入配置模块 ConfigModule,在imports中配置调用ConfigModule的静态方法register,并传入配置{ folder: '../../src/env/.env' }

2. 静态方法名

在上面的示例中,使用register定义了静态方法名,其实静态方法名可自由定义,但 nest 约定了 3 种方法名(同步 / 异步),以让开发者通过方法名就能了解该动态模块的作用范围:

同步异步
registerregisterAsync
forRootforRootAsync
forFeatureforFeatureAsync

forRoot:全局动态模块,在应用程序的根模块中配置和注册服务,只配置一次。

forFeature:局部动态模块,通常用于特定的功能模块中。

register:适合每次使用,都传递不同的配置。

6. 循环依赖模块

当两个类互相依赖时就会出现循环依赖. 例如,当 A 类需要 B 类,而 B 类也需要 A 类时,就会产生循环依赖。

1. 模块之间的相互引用

1. 创建两个模块

nest g mo a
nest g mo b

2. 两个模块相互引用

修改a.module.ts,引入b模块

import { Module } from '@nestjs/common';
import { BModule } from '../b/b.module';

@Module({
  imports: [BModule],
})
export class AModule {}

修改b.module.ts,引入a模块

import { Module } from '@nestjs/common';
import { AModule } from '../a/a.module';

@Module({
  imports: [AModule],
})
export class BModule {}

启动服务:yarn start:dev,控制台会报错

Nest.js 从零到壹详细系列(三):模块ModuleNest 用模块来组织应用程序结构,可以理解为前端的组件。Nes

报错原因:NestJS 在启动时会尝试解析所有的依赖关系。对于每一个模块,它都会遍历该模块中的所有提供者 (providers) 并确保它们所依赖的其他提供者已经被实例化。如果存在循环引用,这意味着某些提供者永远不可能被实例化,因为它们总是等待着尚未被实例化的提供者。

解决方法:使用 forwardRef 函数告诉 NestJS 先单独创建两个module,都创建成功之后再进行引入

3. 使用forwardRef

修改a.module.ts,引入b模块

import { Module, forwardRef } from '@nestjs/common';
import { BModule } from '../b/b.module';

@Module({
  imports: [forwardRef(() => BModule)],
})
export class AModule {}

修改b.module.ts,引入a模块

import { Module, forwardRef } from '@nestjs/common';
import { AModule } from '../a/a.module';

@Module({
  imports: [forwardRef(() => AModule)],
})
export class BModule {}

2. service之间的相互引用

除了Module 之间会循环依赖,provider 之间也会有循环引用的异常报错问题

1. 创建两个service

nest g s a --no-spec 
nest g s b --no-spec 

2. 分別导出自身的service

修改a.module.ts,导出AService

import { Module, forwardRef } from '@nestjs/common';
import { BModule } from '../b/b.module';
import { AService } from './a.service';

@Module({
  imports: [forwardRef(() => BModule)],
  providers: [AService],
  exports: [AService],
})
export class AModule {}

修改b.module.ts,导出BService

import { Module, forwardRef } from '@nestjs/common';
import { AModule } from '../a/a.module';
import { BService } from './b.service';

@Module({
  imports: [forwardRef(() => AModule)],
  providers: [BService],
  exports: [BService],
})
export class BModule {}

3. 两个service相互引用

service之前的相互引用,同样使用forwardRef解决,否则会报错

修改a.service.ts

import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { BService } from 'src/b/b.service';

@Injectable()
export class AService {
  constructor(
    @Inject(forwardRef(() => BService))
    private readonly bService: BService,
  ) {}
}

修改b.service.ts

import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { AService } from 'src/a/a.service';

@Injectable()
export class BService {
  constructor(
    @Inject(forwardRef(() => AService)) 
    private readonly aService: AService,
  ) {}
}

结尾

后续会继续更新Nest相关的文章,对Nest感兴趣的,可先关注我。

往前回顾:

转载自:https://juejin.cn/post/7410599171122823207
评论
请登录