## 自定義提供者
在前面幾章中,我們討論了依賴注入(`DI`)的各個方面,以及如何在 `Nest` 中使用它。其中一個例子是基于[構造函數](https://docs.nestjs.com/providers#dependency-injection)的依賴注入,用于將實例(通常是服務提供者)注入到類中。當您了解到依賴注入是以一種基本的方式構建到 `Nest` 內核中時,您不會感到驚訝。到目前為止,我們只探索了一個主要模式。隨著應用程序變得越來越復雜,您可能需要利用 `DI` 系統的所有特性,因此讓我們更詳細地研究它們。
### DI 基礎[#](di-fundamentals)
依賴注入是一種控制反轉(`IoC`)技術,您可以將依賴的實例化委派給 `IoC` 容器(在我們的示例中為 `NestJS` 運行時系統),而不是必須在自己的代碼中執行。 讓我們從[“提供者”](providers.md)一章中檢查此示例中發生的情況。
首先,我們定義一個提供者。`@Injectable()`裝飾器將 `CatsService` 類標記為提供者。
> cats.service.ts
```typescript
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
findAll(): Cat[] {
return this.cats;
}
}
```
然后,我們要求 `Nest` 將提供程序注入到我們的控制器類中:
> cats.controller.ts
```typescript
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
```
最后,我們在 `Nest IoC` 容器中注冊提供程序
> app.module.ts
```typescript
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
```
這個過程有三個關鍵步驟:
1. 在 `cats.service.ts` 中 `@Injectable()` 裝飾器聲明 `CatsService` 類是一個可以由`Nest IoC`容器管理的類。
2. 在 `cats.controller.ts` 中 `CatsController` 聲明了一個依賴于 `CatsService` 令牌(`token`)的構造函數注入:
```typescript
constructor(private readonly catsService: CatsService)
```
3. 在 `app.module.ts` 中,我們將標記 `CatsService`與 `cats.service.ts`文件中的 `CatsService` 類相關聯。 我們將在下面確切地看到這種關聯(也稱為注冊)的發生方式。
當 `Nest IoC` 容器實例化 `CatsController` 時,它首先查找所有依賴項*。 當找到 `CatsService` 依賴項時,它將對 `CatsService`令牌(`token`)執行查找,并根據上述步驟(上面的#3)返回 `CatsService` 類。 假定單例范圍(默認行為),`Nest` 然后將創建 `CatsService` 實例,將其緩存并返回,或者如果已經緩存,則返回現有實例。
這個解釋稍微簡化了一點。我們忽略的一個重要方面是,分析依賴項代碼的過程非常復雜,并且發生在應用程序引導期間。一個關鍵特性是依賴關系分析(或“創建依賴關系圖”)是可傳遞的。 在上面的示例中,如果 `CatsService` 本身具有依賴項,那么那些依賴項也將得到解決。 依賴關系圖確保以正確的順序解決依賴關系-本質上是“自下而上”。 這種機制使開發人員不必管理此類復雜的依賴關系圖。
### 標準提供者
讓我們仔細看看 `@Module()`裝飾器。在中 `app.module` ,我們聲明:
```typescript
@Module({
controllers: [CatsController],
providers: [CatsService],
})
```
providers屬性接受一個提供者數組。到目前為止,我們已經通過一個類名列表提供了這些提供者。實際上,該語法`providers: [CatsService]`是更完整語法的簡寫:
```typescript
providers: [
{
provide: CatsService,
useClass: CatsService,
},
];
```
現在我們看到了這個顯式的構造,我們可以理解注冊過程。在這里,我們明確地將令牌 `CatsService`與類 `CatsService` 關聯起來。簡寫表示法只是為了簡化最常見的用例,其中令牌用于請求同名類的實例。
### 自定義提供者
當您的要求超出標準提供商所提供的要求時,會發生什么?這里有一些例子:
- 您要創建自定義實例,而不是讓 `Nest` 實例化(或返回其緩存實例)類
- 您想在第二個依賴項中重用現有的類
- 您想使用模擬版本覆蓋類進行測試
`Nest` 可讓您定義自定義提供程序來處理這些情況。它提供了幾種定義自定義提供程序的方法。讓我們來看看它們。
### 值提供者 (useValue)
`useValue` 語法對于注入常量值、將外部庫放入 `Nest` 容器或使用模擬對象替換實際實現非常有用。假設您希望強制 `Nest` 使用模擬 `CatsService` 進行測試。
```typescript
import { CatsService } from './cats.service';
const mockCatsService = {
/* mock implementation
...
*/
};
@Module({
imports: [CatsModule],
providers: [
{
provide: CatsService,
useValue: mockCatsService,
},
],
})
export class AppModule {}
```
在本例中,`CatsService` 令牌將解析為 `mockCatsService` 模擬對象。`useValue` 需要一個值——在本例中是一個文字對象,它與要替換的 `CatsService` 類具有相同的接口。由于 `TypeScript` 的結構類型化,您可以使用任何具有兼容接口的對象,包括文本對象或用 `new` 實例化的類實例。
### 非類提供者
到目前為止,我們已經使用了類名作為我們的提供者標記( `providers` 數組中列出的提供者中的 `Provide` 屬性的值)。 這與基于構造函數的注入所使用的標準模式相匹配,其中令牌也是類名。 (如果此概念尚不完全清楚,請參閱[DI](https://docs.nestjs.com/fundamentals/custom-providers#di-fundamentals)基礎知識,以重新學習令牌)。 有時,我們可能希望靈活使用字符串或符號作為 `DI` 令牌。 例如:
```typescript
import { connection } from './connection';
@Module({
providers: [
{
provide: 'CONNECTION',
useValue: connection,
},
],
})
export class AppModule {}
```
在本例中,我們將字符串值令牌(`'CONNECTION'`)與從外部文件導入的已存在的連接對象相關聯。
> 除了使用字符串作為令牌之外,還可以使用您還可以使用 JavaScript[符號](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)或 TypeScript[枚舉](https://www.typescriptlang.org/docs/handbook/enums.html)。
我們前面已經看到了如何使用基于標準構造函數的注入模式注入提供者。此模式要求用類名聲明依賴項。`'CONNECTION'` 自定義提供程序使用字符串值令牌。讓我們看看如何注入這樣的提供者。為此,我們使用 `@Inject()` 裝飾器。這個裝飾器只接受一個參數——令牌。
```typescript
@Injectable()
export class CatsRepository {
constructor(@Inject('CONNECTION') connection: Connection) {}
}
```
> `@Inject()`裝飾器是從`@nestjs/common`包中導入的。
雖然我們在上面的例子中直接使用字符串 `'CONNECTION'` 來進行說明,但是為了清晰的代碼組織,最佳實踐是在單獨的文件(例如 `constants.ts` )中定義標記。 對待它們就像對待在其自己的文件中定義并在需要時導入的符號或枚舉一樣。
### 類提供者 (useClass)
`useClass`語法允許您動態確定令牌應解析為的類。 例如,假設我們有一個抽象(或默認)的 `ConfigService` 類。 根據當前環境,我們希望 `Nest 提供配置服務的不同實現。 以下代碼實現了這種策略。
```typescript
const configServiceProvider = {
provide: ConfigService,
useClass:
process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
@Module({
providers: [configServiceProvider],
})
export class AppModule {}
```
讓我們看一下此代碼示例中的一些細節。 您會注意到,我們首先定義對象 `configServiceProvider`,然后將其傳遞給模塊裝飾器的 `providers` 屬性。 這只是一些代碼組織,但是在功能上等同于我們到目前為止在本章中使用的示例。
另外,我們使用 `ConfigService` 類名稱作為令牌。 對于任何依賴 `ConfigService` 的類,`Nest` 都會注入提供的類的實例( `DevelopmentConfigService` 或 `ProductionConfigService`),該實例將覆蓋在其他地方已聲明的任何默認實現(例如,使用 `@Injectable()` 裝飾器聲明的 `ConfigService`)。
### 工廠提供者 (useFactory)
`useFactory` 語法允許動態創建提供程序。實工廠函數的返回實際的 `provider` 。工廠功能可以根據需要簡單或復雜。一個簡單的工廠可能不依賴于任何其他的提供者。更復雜的工廠可以自己注入它需要的其他提供者來計算結果。對于后一種情況,工廠提供程序語法有一對相關的機制:
1. 工廠函數可以接受(可選)參數。
2. `inject` 屬性接受一個提供者數組,在實例化過程中,`Nest` 將解析該數組并將其作為參數傳遞給工廠函數。這兩個列表應該是相關的: `Nest` 將從 `inject` 列表中以相同的順序將實例作為參數傳遞給工廠函數。
下面示例演示:
~~~typescript
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider, optionalProvider?: string) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
// \_____________/ \__________________/
// This provider The provider with this
// is mandatory. token can resolves to `undefined`.
};
@Module({
providers: [
connectionFactory,
OptionsProvider,
// { provide: 'SomeOptionalProvider', useValue: 'anything' },
],
})
export class AppModule {}
~~~
### 別名提供者 (useExisting)
`useExisting` 語法允許您為現有的提供程序創建別名。這將創建兩種訪問同一提供者的方法。在下面的示例中,(基于`string`)令牌 `'AliasedLoggerService'` 是(基于類的)令牌 `LoggerService` 的別名。假設我們有兩個不同的依賴項,一個用于 `'AlilasedLoggerService'` ,另一個用于 `LoggerService` 。如果兩個依賴項都用單例作用域指定,它們將解析為同一個實例。
```typescript
@Injectable()
class LoggerService {
/* implementation details */
}
const loggerAliasProvider = {
provide: 'AliasedLoggerService',
useExisting: LoggerService,
};
@Module({
providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}
```
### 非服務提供者
雖然提供者經常提供服務,但他們并不限于這種用途。提供者可以提供任何值。例如,提供程序可以根據當前環境提供配置對象數組,如下所示:
```typescript
const configFactory = {
provide: 'CONFIG',
useFactory: () => {
return process.env.NODE_ENV === 'development'
? devConfig
: prodConfig;
},
};
@Module({
providers: [configFactory],
})
export class AppModule {}
```
### 導出自定義提供者
與任何提供程序一樣,自定義提供程序的作用域僅限于其聲明模塊。要使它對其他模塊可見,必須導出它。要導出自定義提供程序,我們可以使用其令牌或完整的提供程序對象。
以下示例顯示了使用 `token` 的例子:
```typescript
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
exports: ['CONNECTION'],
})
export class AppModule {}
```
但是你也可以使用整個對象:
```typescript
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
exports: [connectionFactory],
})
export class AppModule {}
```
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?