1- ### 动态模块
1+ # 动态模块
22
3- [ 模块章节] ( /modules ) 介绍了 Nest 模块的基础知识,并简要提及了[ 动态模块] ( .. /overview/modules#动态模块) 。本章将深入探讨动态模块的主题。学习完成后,您将充分理解它们的概念、使用方法及适用场景。
3+ [ 模块章节] ( /overview/ modules ) 介绍了 Nest 模块的基础知识,并简要提及了[ 动态模块] ( /overview/modules#动态模块 ) 。本章将深入探讨动态模块的主题。学习完成后,您将充分理解它们的概念、使用方法及适用场景。
44
55#### 介绍
66
7- 文档** 概述** 部分中的大多数应用代码示例都使用常规(静态)模块。模块定义了如[ 提供者] ( /providers ) 和[ 控制器] ( /controllers ) 等组件的集合,这些组件作为整体应用的模块化部分协同工作。它们为这些组件提供了执行上下文或作用域。例如,模块中定义的提供者对该模块的其他成员可见,无需显式导出。当提供者需要在模块外部可见时,需先从其宿主模块导出,再导入到消费模块中。
7+ 文档** 概述** 部分中的大多数应用代码示例都使用常规(静态)模块。模块定义了如[ 提供者] ( /overview/ providers ) 和[ 控制器] ( /overview /controllers) 等组件的集合,这些组件作为整体应用的模块化部分协同工作。它们为这些组件提供了执行上下文或作用域。例如,模块中定义的提供者对该模块的其他成员可见,无需显式导出。当提供者需要在模块外部可见时,需先从其宿主模块导出,再导入到消费模块中。
88
99让我们通过一个熟悉的示例来说明。
1010
@@ -53,31 +53,29 @@ export class AuthService {
5353
5454我们将这称为** 静态** 模块绑定。Nest 连接模块所需的所有信息都已经在宿主模块和消费模块中声明完毕。让我们解析这个过程发生了什么。Nest 通过以下方式使 ` UsersService ` 在 ` AuthModule ` 中可用:
5555
56- 1 . 实例化 ` UsersModule ` ,包括递归导入 ` UsersModule ` 自身消费的其他模块,并递归解析所有依赖项(参见[ 自定义提供者] ( .. /fundamentals/dependency-injection) )。
56+ 1 . 实例化 ` UsersModule ` ,包括递归导入 ` UsersModule ` 自身消费的其他模块,并递归解析所有依赖项(参见[ 自定义提供者] ( /fundamentals/dependency-injection ) )。
57572 . 实例化 ` AuthModule ` ,并使 ` UsersModule ` 导出的提供者可用于 ` AuthModule ` 中的组件(就像它们原本就是在 ` AuthModule ` 中声明的一样)。
58583 . 在 ` AuthService ` 中注入 ` UsersService ` 的实例。
5959
6060#### 动态模块使用场景
6161
62- 使用静态模块绑定时,消费模块无法** 影响** 主机模块中提供者的配置方式 。为什么这一点很重要?考虑这种情况:我们有一个通用模块需要在不同使用场景中表现出不同行为。这与许多系统中的"插件"概念类似,通用功能需要先进行某些配置才能被消费者使用。
62+ 使用静态模块绑定时,消费模块无法** 影响** 提供者在宿主模块中的配置方式 。为什么这一点很重要?考虑这种情况:我们有一个通用模块需要在不同使用场景中表现出不同行为。这与许多系统中的"插件"概念类似,通用功能需要先进行某些配置才能被消费者使用。
6363
64- Nest 框架中的一个典型示例是** 配置模块** 。许多应用程序发现通过使用配置模块来外部化配置细节非常有用 。这使得在不同部署环境中动态更改应用设置变得简单:例如为开发者使用开发数据库,为预发布/测试环境使用预发布数据库等。通过将配置参数的管理委托给配置模块,应用程序源代码得以与配置参数保持独立。
64+ Nest 框架中的一个典型示例是** 配置模块** 。许多应用程序发现,通过使用配置模块将配置细节外部化非常有用 。这使得在不同部署环境中动态更改应用设置变得简单:例如为开发者使用开发数据库,为预发布/测试环境使用预发布数据库等。通过将配置参数的管理委托给配置模块,应用程序源代码得以与配置参数保持独立。
6565
66- 挑战在于配置模块本身是通用的(类似于"插件"),需要由使用它的模块进行定制。这正是* 动态模块* 发挥作用的地方。利用动态模块特性,我们可以使配置模块** 动态化** ,这样使用模块就能通过 API 控制在导入时如何定制配置模块。
66+ 挑战在于配置模块本身是通用的(类似于"插件"),需要由使用它的模块进行定制。这正是** 动态模块* * 发挥作用的地方。利用动态模块特性,我们可以使配置模块** 动态化** ,这样使用模块就能通过 API 控制在导入时如何定制配置模块。
6767
6868换句话说,动态模块提供了一个 API 用于将一个模块导入另一个模块,并在导入时定制该模块的属性和行为,这与我们目前所见的静态绑定方式形成对比。
6969
70- <app-banner-devtools ></app-banner-devtools >
71-
7270#### 配置模块示例
7371
74- 我们将使用[ 配置章节] ( .. /techniques/configuration#服务) 中示例代码的基础版本作为本节内容。本章节完成后的最终版本可在此处获取[ 完整示例] ( https://github.com/nestjs/nest/tree/master/sample/25-dynamic-modules ) 。
72+ 我们将使用[ 配置章节] ( /techniques/configuration#服务 ) 中示例代码的基础版本作为本节内容。本章节完成后的最终版本可在此处获取[ 完整示例] ( https://github.com/nestjs/nest/tree/master/sample/25-dynamic-modules ) 。
7573
7674我们的需求是让 ` ConfigModule ` 能够接收一个 ` options ` 对象来实现自定义功能。以下是我们要支持的特性:基础示例中将 ` .env ` 文件的位置硬编码为项目根目录。假设我们希望使其可配置,这样您就可以将 ` .env ` 文件存放在任意选择的文件夹中。例如,您可能希望将各种 ` .env ` 文件存储在项目根目录下名为 ` config ` 的文件夹中(即与 ` src ` 文件夹同级)。您希望在不同项目中使用 ` ConfigModule ` 时能够选择不同的文件夹。
7775
7876动态模块使我们能够向导入的模块传递参数,从而改变其行为。让我们看看这是如何实现的。如果从消费模块的角度出发,先设想最终效果,再逆向推导实现方式,会更有帮助。首先,快速回顾一下* 静态* 导入 ` ConfigModule ` 的示例(即无法影响被导入模块行为的传统方式)。请特别注意 ` @Module() ` 装饰器中 ` imports ` 数组的写法:
7977
80- ``` typescript
78+ ``` typescript title="app.module.ts"
8179import { Module } from ' @nestjs/common' ;
8280import { AppController } from ' ./app.controller' ;
8381import { AppService } from ' ./app.service' ;
@@ -93,7 +91,7 @@ export class AppModule {}
9391
9492现在让我们思考一下* 动态模块* 导入(传入配置对象的情况)会是什么样子。比较这两个示例中 ` imports ` 数组的区别:
9593
96- ``` typescript
94+ ``` typescript title="app.module.ts"
9795import { Module } from ' @nestjs/common' ;
9896import { AppController } from ' ./app.controller' ;
9997import { AppService } from ' ./app.service' ;
@@ -211,7 +209,7 @@ export class ConfigService {
211209
212210现在我们的 ` ConfigService ` 已经知道如何在 ` options ` 中指定的文件夹里找到 ` .env ` 文件。
213211
214- 我们剩下的任务是如何将 ` register() ` 步骤中的 ` options ` 对象注入到 ` ConfigService ` 中。当然,我们会使用* 依赖注入* 来实现这一点。这是关键点,请务必理解。我们的 ` ConfigModule ` 提供了 ` ConfigService ` ,而 ` ConfigService ` 又依赖于仅在运行时提供的 ` options ` 对象。因此,在运行时,我们需要先将 ` options ` 对象绑定到 Nest IoC 容器,然后让 Nest 将其注入到 ` ConfigService ` 中。记得在** 自定义提供者** 章节中提到的,提供者可以[ 包含任何值] ( .. /fundamentals/dependency-injection#非基于服务的提供者) ,而不仅仅是服务,所以我们可以放心使用依赖注入来处理简单的 ` options ` 对象。
212+ 我们剩下的任务是如何将 ` register() ` 步骤中的 ` options ` 对象注入到 ` ConfigService ` 中。当然,我们会使用* 依赖注入* 来实现这一点。这是关键点,请务必理解。我们的 ` ConfigModule ` 提供了 ` ConfigService ` ,而 ` ConfigService ` 又依赖于仅在运行时提供的 ` options ` 对象。因此,在运行时,我们需要先将 ` options ` 对象绑定到 Nest IoC 容器,然后让 Nest 将其注入到 ` ConfigService ` 中。记得在** 自定义提供者** 章节中提到的,提供者可以[ 包含任何值] ( /fundamentals/dependency-injection#非基于服务的提供者 ) ,而不仅仅是服务,所以我们可以放心使用依赖注入来处理简单的 ` options ` 对象。
215213
216214我们先解决将选项对象绑定到 IoC 容器的问题。这需要在静态的 ` register() ` 方法中完成。注意我们正在动态构建一个模块,而模块的属性之一就是它的提供者列表。因此我们需要将选项对象定义为一个提供者,这样它就能被注入到 ` ConfigService ` 中(下一步会用到这个特性)。在下面代码中,请特别注意 ` providers ` 数组:
217215
@@ -237,7 +235,7 @@ export class ConfigModule {
237235}
238236```
239237
240- 现在我们可以通过向 ` ConfigService ` 注入 ` 'CONFIG_OPTIONS' ` 提供者来完成整个过程。注意当使用非类令牌定义提供者时,需要按照[ 这里的说明] ( .. /fundamentals/dependency-injection#非基于类的提供者令牌) 使用 ` @Inject() ` 装饰器。
238+ 现在我们可以通过向 ` ConfigService ` 注入 ` 'CONFIG_OPTIONS' ` 提供者来完成整个过程。注意当使用非类令牌定义提供者时,需要按照[ 这里的说明] ( /fundamentals/dependency-injection#非基于类的提供者令牌 ) 使用 ` @Inject() ` 装饰器。
241239
242240``` typescript
243241import * as dotenv from ' dotenv' ;
0 commit comments