NestJS 是一个专门用 TypeScript 构建的 Web 框架。大多数有经验的开发人员都会认出 NestJS 中熟悉的东西;它是一个强大的、面向对象的编程模型,它的语法与另一个框架 Angular 非常相似。
NestJS 代码通常会迫使您创建最佳设计模式。从 Java 或 ASP.NET 背景过渡到 Node.js 和 TypeScript 的开发人员将很快了解 NestJS 的结构。NestJS 中都有依赖注入、封装、类和类注解或装饰器等概念。
在 NestJS 中,创建一个可重用的自定义模块非常简单,最重要的是,我们鼓励这样做。将模块封装成小的、可重用的块将有助于提高 NestJS 应用程序的开发速度。而且,如果您决定将这些模块作为开源软件包发布到野外,您将获得一些布朗尼积分。
在本文中,我们将探索 NestJS 中的模块,创建我们自己的基本、可配置模块。接下来,您可以访问GitHub 上的完整代码。让我们开始吧!
- 什么是 NestJS 模块?
- NestJS 模块状态管理
- NestJS 模块注入范围
- 使用单例时防止数据竞争情况
- NestJS 中模块的注入范围
- NestJS 中的动态模块
- 构建一个基本的可配置 NestJS 模块
- 如何创建一个基本的可配置 NestJS 模块
什么是 NestJS 模块?
模块是一组封装的代码,可注入到 NestJS 应用程序中。您可以使用模块来创建旨在执行特定任务的自定义服务。例如,TypeORM是基于 TypeScript 的 ORM。NestJS 团队创建了一个模块,该模块将注入一个开放的数据库连接,允许来自注入模块的数据库命令和查询。
NestJS 模块是框架强大的依赖注入机制的支柱。依赖注入是一种应用程序开发模式,旨在解耦两个类或模块之间的依赖关系。
您可以使用接口来指定一种关于您的依赖项应如何表现的契约,而不是为每个类严格定义依赖项,而无需对其应如何运行进行字面定义。最后,解耦架构允许通用应用程序,并为应用程序中的每个模块创建即插即用行为。
NestJS 模块状态管理
默认情况下,NestJS 模块是单例的,这意味着您只需要启动一个模块一次。虽然从工程的角度来看,为每个模块创建单例似乎太过分了,但 NestJS在组件级别初始化单例。
NestJS 中的模块范围
在 NestJS 中,模块的注入范围分为三种:
- 请求级模块
- 组件级模块或瞬态模块
- 共享应用程序级模块
默认情况下,大多数 NestJS 模块都是应用程序级模块,也称为全局共享模块。但是,并不是每个模块都可以是全局模块。其中一些需要保持瞬态或请求级模块。
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →
例如,如果您需要一个应用程序级的只读模块,最好的办法是使用全局共享模块。存储在模块中的数据不会经常更改,因此可以将其推迟为应用程序级单例以节省内存并创建全局可访问的类。带有@Global装饰器的模块消除了代码和组件级别的冗余,因为您不需要重新初始化模块。
为了更好地理解模块化级别的状态保存,如果模块中有一个具有瞬态或请求范围的常量,它将是一个不可变变量,直到模块在垃圾收集时将其销毁。但是,当使用跨越整个应用程序的全局模块时,它只会在应用程序的生命周期结束时被销毁。
使用单例时防止数据竞争情况
使用单例时要注意的另一件事是数据竞争问题。Node.js 不能免受数据竞争的影响,NestJS 也不能。
当两个独立的进程尝试同时更新同一个数据块时,就会出现数据竞争情况。由于对象可全局访问,因此同时执行数据可能会导致执行时丢失数据点。
避免数据竞争条件的最佳实践包括创建一个全局只读模块并更加仔细地考虑每个模块的注入范围。全局模块最容易受到数据竞争条件的影响,使用全局模块在组件之间进行通信或管理状态将导致反模式。
但是,为什么瞬态组件级模块不能这样说呢?在组件级别,封装屏障仅扩展到组件的需求。每个瞬态模块提供程序都有一个专用实例。组件级别的关注点分离通常更细化,使其比大型应用程序更可预测。请求级别的单例也可以这样说,尽管规模较小。
NestJS 模块注入范围
综上所述,NestJS 中模块的注入范围有以下三种:
- 请求级模块
- 组件级模块:瞬态
- 共享应用级模块:全局
每个都有其优点和缺点,数据竞争是全局模块最常见的问题。大多数全局模块应该是只读的,NestJS 在初始化时只会设置一次原始状态。
组件级模块有更多细微差别。更具体地说,您可以将它们用于较小规模的状态管理,因为它们具有可预测性。单例在组件级别提供的精细封装使其成为组件级别状态管理的完美选择。
请记住,数据竞争条件仅限于每个独立模块的状态。在数据库等外部应用程序中修改数据应该不是问题,因为数据库有自己的数据竞争解决方案。
NestJS 中的动态模块
默认 NestJS 模块是静态的并且不可配置。可配置模型构建器本质上是动态模块工厂,可以根据初始化时传递的变量生成不同的模块。
来自 LogRocket 的更多精彩文章:
- 不要错过来自 LogRocket 的精选时事通讯The Replay
- 使用 React 的 useEffect优化应用程序的性能
- 在多个 Node 版本之间切换
- 了解如何使用 AnimXYZ 为您的 React 应用程序制作动画
- 探索 Tauri,一个用于构建二进制文件的新框架
- 比较NestJS 与 Express.js
- 发现TypeScript 领域中使用的流行 ORM
在开始使用可配置模块之前,您需要了解动态模块的基础知识。它们的用例通常围绕创建可以从外部 API 接收参数以更改模块行为方式的非静态模块,特别是每个模块处理数据的方式。
例如,假设您创建了一个用于从数据库查询数据的模块,但您不想为特定的数据库提供程序硬编码它。你如何解决问题?
首先,您需要创建一个具有配置功能的模块。配置函数将有一个数据库提供程序接口作为参数,它具有应用程序连接和查询数据库所需的所有基本功能。因为使用接口作为参数,所以只要提供者扩展了接口,就可以注入不同的数据库提供者。
底层业务逻辑仍然相同,但数据库提供者将根据您在初始化时提供的提供者进行更改。因此,您的模块将不再是静态的,而是动态的。这就是为什么所有可配置的模块在底层都是动态模块的原因。
构建一个基本的可配置 NestJS 模块
例如,我们将创建一个自定义 NestJS 模块,该模块使用包中的 API从.env文件中读取数据。该模块将用作可在项目中使用的可配置代理:process.env``dotenv
模块架构
代理模块的架构似乎是多余的,因为您可以process.env直接访问变量而无需依赖注入。但是,为了简单起见,您将使用此架构来完全掌握 NestJS 模块的工作原理。
您的代理模块将检索process.envon 初始化并将其存储在其env属性中。NestJS 模块默认是单例的,所以你只需要初始化一次。您可以执行该getEnv函数来检索您的env变量。它将作为动态env属性的吸气剂。
您可以在初始化时添加一个函数来接受参数并创建一个动态模块,使其可配置。在这种情况下,该withConfig功能将是可配置的init功能。
如何创建一个基本的可配置 NestJS 模块
@nest/cli使用以下命令全局安装:
npm i -g @nest/cli
然后,生成一个新的 NestJS 应用:
嵌套新的可配置模块构建器示例
您可以选择您喜欢的包管理器,但在本教程中,我们将主要使用 Yarn。step-1您可以通过转到分支来查看到目前为止生成的代码。
新的 NestJS 项目在一个层次上包含所有模块;您需要重构它才能继续。将模块、控制器和服务复制到名为api-modules并将所有文件和变量名称从重命名为 的文件夹App中Api。
创建一个新AppModule文件并在导入中注入ApiModule如下:
从'@nestjs/common'导入{模块};
从'./api-module/api.module'导入{ApiModule};
@模块({
进口:[ApiModule],
})
导出类 AppModule {}
如果您未能跟进,请检查存储库中的step-2分支。现在,您可以开始创建process.env代理模块了。您需要该dotenv包来访问.env文件,因此通过运行以下命令来安装依赖项:
纱线添加 dotenv
创建一个名为的新文件夹src/env-proxy-module并创建两个文件,以env-proxy.module.ts:
从'@nestjs/common'导入{全局,模块};
从'./env-proxy.service'导入{EnvProxyService};
@全球的()
@模块({
提供者:[EnvProxyService],
出口:[EnvProxyService],
})
导出类 EnvProxyModule {}
请注意,@Global装饰器用于自动将模块的导出注入到注入组件的任何子组件。您不必EnvProxyModule在每个模块上重复导入。相反,您只需将其作为导入添加到 main 中AppModule:
从'./env-proxy-module/env-proxy.module'导入{EnvProxyModule};
@模块({
进口:[ApiModule,EnvProxyModule],
})
导出类 AppModule {}
然后,创建服务文件,env-proxy.service.ts:
从'@nestjs/common'导入{可注射};
需要('dotenv').config(); // eslint-disable-line
@Injectable()
导出类 EnvProxyService {
公共只读环境:NodeJS.ProcessEnv;
构造函数(){
this.env = 进程.env;
}
}
要测试EnvProxyModule,您可以创建一个包含参数的.env文件:DATA
数据=你好世界
GET通过在请求中返回环境变量来试用模块:
从'../env-proxy-module/env-proxy.service'导入{EnvProxyService};
@Injectable()
导出类 APIService {
构造函数(私有只读 envProxy:EnvProxyService){}
getHello(): 字符串 {
返回 this.envProxy.env.DATA;
}
}
现在,运行开发服务器:
纱线开始:开发
打开浏览器并转到localhost:3000。你应该得到一个“你好,世界!” 文本作为回报。step-3您可以在分支中查看此步骤的完整代码。
我们已经创建了一个简单的模块,但它还不能配置。您可以使用ConfigurationModuleBuilder来执行此操作。这ConfigurableModuleBuilder是 NestJS v9 中提供的一项新功能,其目的是减少创建可配置动态模块所需编写的样板代码量。
在里面src/env-proxy-module,创建一个名为的新文件env-proxy.definition.ts:
从“@nestjs/common”导入 { ConfigurableModuleBuilder };
导出接口 EnvProxyModuleOptions {
排除:字符串[];
}
导出常量 { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
新的 ConfigurableModuleBuilder<EnvProxyModuleOptions>({
moduleName: 'EnvProxy',
})
。建造();
该EnvProxyModuleOptions接口代表您在初始化时通过配置文件传递的对象。在这种情况下,您将传递要从模块中排除的环境变量数组。
使EnvProxyModule扩展ConfigurableModuleBuilder如下:
@全球的()
@模块({
提供者:[EnvProxyService],
出口:[EnvProxyService],
})
导出类 EnvProxyModule 扩展 ConfigurableModuleClass {}
继续EnvProxyService上课并实现以下代码:
@Injectable()
导出类 EnvProxyService {
公共只读环境:NodeJS.ProcessEnv;
构造函数(@Inject(MODULE_OPTIONS_TOKEN)私有选项:EnvProxyModuleOptions){
this.env = 进程.env;
options.exclude.forEach(val => {
删除 this.env[val];
});
}
}
我们EnvProxyModuleOptions从构造函数和@Inject装饰器中检索对象。然后,我们将排除环境变量的主要业务逻辑添加到EnvProxyService构造函数中。要对其进行测试,您可以创建第二个名为的环境变量DATA2:
数据=你好世界 DATA2=你好世界2
在AppModule导入时,添加一个register函数并插入一个EnvProxyModuleOptions:
进口:[ApiModule, EnvProxyModule.register({ 排除: [ “数据” ] })],
该模块将排除DATA变量以检查排除是否有效。编辑ApiService如下:
getHello(): 字符串 { 返回 this.envProxy.env.DATA ?? this.envProxy.env.DATA2; }
当你访问时localhost:3000,你会得到Hello World2。
至此,您已经使用可配置模块构建器创建了您的第一个可配置模块!您可以在此处查看最终代码库。
如果您的配置比前面的示例更复杂,则可以改用工厂模式:
从'./env-proxy-module/env-proxy.module'导入{EnvProxyModule};
@模块({
进口:[ApiModule,EnvProxyModule.registerAsync({
使用工厂:异步()=> {
返回 {
排除: [
“数据”
]
}
}
})],
})
导出类 AppModule {}
useFactory允许您调用异步代码来帮助您配置构建器。您可以在 GitHub 上找到最终的异步示例。
概括
NestJS 是一个用 TypeScript 构建的面向对象的 Web 框架。NestJS 在底层使用了强大的面向对象原则并提供了许多功能,包括依赖注入、类、通过装饰器的类注释和强封装。
NestJS 中的状态管理在模块化级别上有所不同。大多数模块的封装仅限于组件级别,而很少有应用程序级别的全局共享状态。所有 NestJS 模块都在底层使用单例。如果没有正确制作,拥有可以全局使用的共享模块可能会导致数据竞争条件。
大多数共享模块应该是只读的,但在某些情况下共享模块是实用的,可以记录并连接到消息队列。否则,您只需要组件级别的模块。
动态模块是非静态可配置模块。本质上,所有可配置模块都是动态的。一个可配置的模块使用工厂模式根据初始化时给出的参数创建不同的模块。
编写自定义 NestJS 模块非常简单。您可以访问 GitHub 上的代码示例,并且可以使用不同的分支来导航项目的每个阶段。希望您喜欢这篇文章,如果您有任何问题,请发表评论。快乐编码!
LogRocket主动显示和诊断您的应用程序和网站中最重要的问题
成千上万的工程和产品团队使用LogRocket来减少了解技术和可用性问题的根本原因所需的时间。使用 LogRocket,您将减少与客户来回对话的时间,并消除无休止的故障排除过程。LogRocket 让您可以花更多时间构建新事物,而减少修复错误的时间。