Nest.js 从零到壹详细系列(三):模块ModuleNest 用模块来组织应用程序结构,可以理解为前端的组件。Nes
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 种方法名(同步 / 异步),以让开发者通过方法名就能了解该动态模块的作用范围:
同步 | 异步 |
---|---|
register | registerAsync |
forRoot | forRootAsync |
forFeature | forFeatureAsync |
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,控制台会报错
报错原因: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