一、基礎定義與核心作用
1.1 什么是@Injectable?
@Injectable()
是 NestJS 依賴注入(Dependency Injection, DI)系統的核心裝飾器,用于將類標記為可注入的提供者(Provider)。它告知 NestJS 的 IoC(控制反轉)容器:該類需要被實例化并管理其依賴關系。
1.2 核心功能
- 依賴注入(DI):通過構造函數自動注入依賴項,實現類之間的松耦合。
- 提供者注冊:將類注冊到 NestJS 的提供者容器中,使其可在其他組件(如控制器、服務)中被注入和使用。
- 生命周期管理:結合作用域(Scope)控制實例的創建和銷毀時機。
1.3 典型示例
import { Injectable } from '@nestjs/common';@Injectable()
export class LoggerService {log(message: string) {console.log(`[Logger] ${message}`);}
}@Injectable()
export class UserService {constructor(private readonly logger: LoggerService) {}getUser() {this.logger.log('Fetching user...');return { id: 1, name: 'John' };}
}
二、高級特性詳解
2.1 作用域(Scope)
NestJS 提供三種作用域,控制提供者實例的生命周期:
2.1.1 DEFAULT(單例)
- 行為:應用啟動時創建一次實例,整個生命周期內復用。
- 適用場景:無狀態服務(如日志服務、配置服務)。
- 配置:
@Injectable() // 默認即為單例 export class ConfigService {// 無狀態配置邏輯 }
2.1.2 REQUEST
- 行為:每個 HTTP 請求創建新實例,實例僅在當前請求內有效。
- 適用場景:需維護請求上下文的服務(如用戶會話、請求級緩存)。
- 配置:
import { Injectable, Scope } from '@nestjs/common';@Injectable({ scope: Scope.REQUEST }) export class UserSessionService {private userId: string;setUserId(id: string) {this.userId = id;}getUserId() {return this.userId;} }
2.1.3 TRANSIENT
- 行為:每次注入時創建新實例。
- 適用場景:需要新鮮實例的場景(如臨時日志記錄器)。
- 配置:
@Injectable({ scope: Scope.TRANSIENT }) export class TempLoggerService {log(message: string) {console.log(`[TempLog] ${message}`);} }
2.2 自定義提供者
通過模塊的 providers
數組靈活配置依賴:
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';@Module({providers: [UserService,{provide: 'CustomLogger',useClass: LoggerService, // 使用已有類},{provide: 'Config',useValue: { apiKey: '123' }, // 靜態值},{provide: 'Database',useFactory: () => {// 工廠函數,可依賴其他提供者const config = new ConfigService();return new DatabaseConnection(config);},inject: [ConfigService], // 注入其他提供者},],exports: [UserService, 'CustomLogger'], // 導出供其他模塊使用
})
export class AppModule {}
三、常見問題與解決方案
3.1 依賴未解析錯誤
錯誤:Nest can't resolve dependencies of the UserService (?)
原因:依賴未正確注冊到模塊的 providers
中。
解決方案:
@Module({providers: [UserService, LoggerService], // 確保所有依賴已注冊controllers: [UserController],
})
export class UserModule {}
3.2 循環依賴
錯誤:兩個服務相互依賴(A → B → A)。
解決方案:
- 重構代碼:將共享邏輯提取到第三方服務。
- 使用
forwardRef
:@Module({imports: [forwardRef(() => OtherModule)], })
3.3 作用域沖突
問題:單例服務依賴請求作用域服務。
解決方案:
- 使用
@Inject(CONTEXT)
:import { INJECTABLE_METADATA } from '@nestjs/common';@Injectable() export class SingletonService {constructor(@Inject(CONTEXT) private context: any) {} }
四、與Angular的對比
4.1 相似性
- DI機制:均使用
@Injectable()
和@Inject()
裝飾器。 - 模塊化:通過模塊(Module)組織依賴關系。
- 作用域:支持類似的作用域配置(如 Angular 的
providedIn
)。
4.2 差異
特性 | NestJS | Angular |
---|---|---|
運行環境 | Node.js 后端 | 瀏覽器前端 |
核心功能 | HTTP 服務器、微服務、GraphQL | SPA 開發、路由、模板引擎 |
作用域默認值 | DEFAULT (單例) | root (單例) |
典型場景 | API 開發、后端服務 | 客戶端應用、組件化開發 |
五、最佳實踐
5.1 模塊化組織
- 按功能劃分模塊:將相關服務、控制器封裝到獨立模塊。
- 導出公共服務:通過
exports
暴露公共提供者,避免全局污染。
5.2 作用域選擇
- 優先單例:無狀態服務使用默認單例作用域,優化性能。
- 請求作用域:需維護請求上下文時使用,注意實例創建開銷。
5.3 測試策略
- 模擬依賴:使用
Test.createTestingModule()
模擬服務:describe('UserService', () => {let service: UserService;const mockLogger = { log: jest.fn() };beforeEach(async () => {const module: TestingModule = await Test.createTestingModule({providers: [UserService,{ provide: LoggerService, useValue: mockLogger },],}).compile();service = module.get<UserService>(UserService);}); });
六、總結
@Injectable()
是 NestJS 實現依賴注入和 IoC 的基石,通過合理使用其作用域、自定義提供者等功能,可構建出高可維護性、可擴展的后端應用。結合模塊化設計和最佳實踐,能進一步提升開發效率和代碼質量。掌握 @Injectable()
的高級特性(如作用域、自定義提供者)是成為 NestJS 高級開發者的關鍵一步。