Nest —— Modules

2,043 阅读7分钟

前言

       之前匆匆写了一篇关于Nest官文的翻译,然而写的时候比较着急,所以很多地方的翻译比较马虎,甚至直接丢进翻译器在丢出来....(有的时候我会被丢进翻译器之后被丢出来的译文吓到,原因是...译文比自己翻译的好/(ㄒoㄒ)/~~)但还是有很多部分是翻译器解决不了的,语句通顺固然优雅锦上添花,但一点小的错误却是致命的。
       现在手头比较悠闲,也打算重新修改一份比较优雅的中文文档。有人会问花这么多时间写这个东西,是不是真的有用。百度上面也有一些关于Nest的文档,完全也不会有人来看你写的翻译。我的感受是,可能这就是我的学习方式吧。其实平时阅读文档,大多数情况下都是脑子说会了,手说不会。一边翻译英文文档,一遍理解框架的含义,还有助于提高阅读英文文档的能力。手敲过一遍和眼睛看过一遍真的不太一样,而且这种方式会增加使用时的自信。之后打算将每一章节分开书写,最后再通过链接汇总到一篇Nest妲己大记中去。就算没有人看,自己想要翻阅文档的时候,也可以拿出来看看,还能改改。这是一种乐趣,就好像养成游戏一样。



正文 Modules

模块是使用装饰器 @Module() 的类。装饰器提供了元数据metadata给Nest来组织整个应用的架构。

每个应用至少需要拥有一个根模块。根模块是Nest绘制整个应用图(内部的数据结构,Nest用于解析模块和providers之间的关系和依赖)的起始点。当然非常小的应用理论上来说确实可能只有一个根模块,但这并不是典型案例。我们想要强调的是, 模块是墙裂推荐来组织你的组件的一种十分高效的方式 。所以对大多数的应用来说,最终的结构会使用多个模块, 每一个模块都是对一系列紧密相关的功能的一个封装

装饰器 @Module() 接受一个用来描述模块的配置对象,包含以下属性:

providers providers通过Nest的注入器实例化,然后至少在当前模块可以共享这些providers
controllers 定义在当前模块的一组需要被实例化的控制器
imports 引入的模块的列表,由其他模块导出的providers并且在当前模块需要使用的模块
exports providers的子集,由当前模块提供的provis,并且在其他引入当前模块的模块中应当能够使用

模块会默认将providers封装进内部,这意味着你不能注入那些 既不是当前模块的部分,又不是从引入的模块中导出过的 providers。所以你可能需要考虑从一个模块中导出一些providers作为所有模块的公共接口或API



Feature modules 功能模块

CatsControllerCatsService 属于相同的应用领域。既然他们紧密相关,将他们放进一个功能模块是具有意义的。功能模块只是简单地组织和特定功能相关的代码,保证了代码组织有序,边界清晰。这有助于我们处理复杂性,并且遵循SOLID原则开发,尤其是当应用的大小随着团队人数而增长时。

// cats/cats.module.ts
import { Module } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'

@Mdoule({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModules {}

Hints
使用CLI来创建模块:$ nest g module cats

上面我们在cats.module.ts文件中定义了 CatsModule 模块,将所有和该模块相关的文件放入cats目录下。最后要做的事情就是在跟模块中将这模块引入。

import { Module } from '@nestjs/common'
import { CatsModule } from './cats/cats.module'

@Module({
  imports: [CatsModule],
})
export class AppModule {}

然后目录结构看起来是这样:



Shared modules 共享模块

在Nest中模块默认是一个单例对象,所以你可以在模块之间共享任何provider的相同实例。

所有的模块都自动成为共享模块,只要创建就能被其他任何模块重用。举例我们需要在几个其他模块中共享 CatsService 的实例。我们先通过在模块exports数组中导出他

// cats.module.ts
import { Module } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'

@Module({
  controlers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

现在任何导入 CatsModule 的模块都可以去使用 CatsService ,并且也可以在其他模块之间共享同一个实例。



Module re-exporting 模块重导出

模块可以导出他们内部的providers,模块也可以重导出他们引入的模块。如下例, CommonModule 模块同时被 CoreModule 引入并导出,那么在其它引入 CoreModule 模块的地方也可以使用该模块。

@Module({
  imports: [CommonModule],
  exports: [CommonModule]
})
export class CoreModule {}



Dependency injection DI 依赖注入

模块的类也可以注入providers

import { Module } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'

@Module({
  controllers: [CatsController],
  providers: [CatsServices],
})
export class CatsModule {
  constructor(private readonly catsService: CatsService) {}
}

然而,模块的类本身不可以被当作providers注入,会导致循环依赖。



Global modules 全局模块

到处引入相同的一组模块是冗余的,Agular不像Nest,他的Providers是注册在全局作用域中的。一旦定义任何地方都可以使用。但是Nest注册Providers是封装进模块作用域的。如果没有引入对应的模块是无法使用这个模块下的Providers的。
当你想要提供一组providers任何地方都可以使用,为这个模块添加装饰器 @Global()

import { Module, Global } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

@Global() 装饰器使这个模块成为全局作用域。 全局模块应该只注册一次 ,通常是在根模块或者核心模块中。在上例, CatsService 将无处不在,模块想要注入这个服务并不需要在 CatsModuleimports中引入。

Hints
全局模块是为了减少那些必须的引用,但不要把所有的东西都放入全局。使用imports数组去引入模块的api始终是更推荐的方式。



Dynamic modules 动态模块 Nest模块系统包含了强大的特性叫做动态模块。这个特性可以使你简单地创建可动态注册配置地自定义模块。这里(ing)涵盖了大部分动态模块的内容。这个章节将简要概述,来完成模块的介绍。
下面是对 DatabaseModule 动态模块的定义的一个例子。

import { Module, DynamicModule } from '@nestjs/common'
import { createDatabaseProviders } from './database.providers'
import { Connection } from './connection.provider'

@Module({
  providers: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities)
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    }
  }
}

Hints
forRoot()方法可能同步或异步地返回一个动态模块。

这个模块默认定义了 Connection provider,但是此外还会取决于传入 forRoot() 方法的entiteisoptions对象,它暴露了一个providers的集合,例如库。注意动态模块返回的这些属性,扩展了(甚至是重载)在装饰器 @Module()中定义的 基本的模块元数据。这就是如何同时从模块导出静态声明的Provider Connection 和动态地生成数据库Provider
一旦定义, DatabaseModule 就能被引入和配置

import { Module } from '@nestjs/common'
import { DatabaseModule } from './database/database.module'
import { User } from './users/entities/user.entity'

@Module({
  imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}

如果你想要再反过来重新导出一个动态模块,你可以省略forRoot这个方法的调用,并放在exports数组中。

import { Module } from '@nestjs/common'
import { DatabaseModule } from './database/database.module'
import { User } from './users/entities/user.entity'

@Modules({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule],
})

更多详见动态模块(ing)





后记

原文地址: docs.nestjs.com/modules


关于本文

  • 文章非复制黏贴,经浏览文档,以自己的理解进行,代码测试,手打书写。本篇为翻译+意译。
  • 用作记录自己曾经学习、思考过的问题的一种笔记。
  • 用作前端技术交流分享。
  • 阅读本文时欢迎随时质疑本文的准确性,将错误的地方告诉我。本人会积极修改,避免文章对读者的误导。

关于我

  • 是一只有梦想的肥柴。
  • 觉得算法、数据结构、函数式编程、js底层原理等十分有趣的小前端。
  • 志同道合的朋友请关注我,一起交流技术,在前端之路上共同成长。
  • 如对本人有任何意见建议尽管告诉我哦~ 初为肥柴,请多多关照~
  • 前端路漫漫,技术学不完。今天也是美(diao)好(fa)的一天( 跪了...orz