我不太喜歡像 NestJS 這樣的大型框架;我一直喜歡以我想要的方式自由地構建我的軟體,並以輕量級的方式決定結構。但在測試 NestJS 時我喜歡的是依賴注入。
依賴注入(DI)是一種設計模式,它允許我們透過消除創建和管理類別依賴關係的責任來開發鬆散耦合的程式碼。這種模式對於編寫可維護、可測試和可擴展的應用程式至關重要。在 TypeScript 生態系統中,TSyringe 作為一個強大且輕量級的依賴注入容器脫穎而出,它簡化了這個過程。
TSyringe 是一個用於 TypeScript/JavaScript 應用程式的輕量級依賴注入容器。由 Microsoft 在其 GitHub (https://github.com/microsoft/tsyringe) 上維護,它使用裝飾器進行建構函式註入。然後,它使用控制反轉容器來儲存基於令牌的依賴項,您可以用該令牌交換實例或值。
在深入了解 TSyringe 之前,我們先簡單探討一下什麼是依賴注入以及它為何如此重要。
依賴注入是一種技術,物件從外部來源接收其依賴項,而不是自己創建它們。這種方法有幾個好處:
首先,讓我們在您的 TypeScript 專案中設定 TSyringe:
npm install tsyringe reflect-metadata
在 tsconfig.json 中,確保有以下選項:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
在應用程式的入口點匯入反射元資料:
import "reflect-metadata";
應用程式的入口點例如是 Next.js 13+ 上的根佈局,也可以是小型 Express 應用程式中的主檔案。
我們以介紹中的範例為例,加入 TSyringe 糖:
讓我們從適配器開始。
// @/adapters/userAdapter.ts import { injectable } from "tsyringe" @injectable() class UserAdapter { constructor(...) {...} async fetchByUUID(uuid) {...} }
注意到 @injectable() 裝飾器了嗎?是告訴TSyringe這個類別可以在運行時注入。
所以我的服務正在使用我們剛剛建立的適配器。讓我們將該適配器注入到我的服務中。
// @/core/user/user.service.ts import { injectable, inject } from "tsyringe" ... @injectable() class UserService { constructor(@inject('UserAdapter') private readonly userAdapter: UserAdapter) {} async fetchByUUID(uuid: string) { ... const { data, error } = await this.userAdapter.fetchByUUID(uuid); ... } }
這裡我還使用了 @injectable 裝飾器,因為 Service 將被注入到我的命令類別中,但我還在構造函數參數中添加了 @inject 裝飾器。此裝飾器告訴 TSyringe 在執行時為 userAdapter 屬性提供令牌 UserAdapter 的實例或值。
最後但並非最不重要的一點是,我的核心的根源:命令類別(通常被錯誤地稱為用例)。
// @/core/user/user.commands.ts import { inject } from "tsyringe" ... @injectable() class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } }
此時,我們已經告訴 TSyringe 將要注入什麼以及要在建構函式中註入什麼。但我們還沒有製作容器來儲存依賴項。我們可以透過兩種方式做到這一點:
我們可以使用依賴注入登錄機碼來建立一個檔案:
// @/core/user/user.dependencies.ts import { container } from "tsyringe" ... container.register("UserService", {useClass: UserService}) // associate the UserService with the token "UserService" container.register("UserAdapter", {useClass: UserAdapter}) // associate the UserAdapter with the token "UserAdapter" export { container }
但是我們也可以使用@registry裝飾器。
// @/core/user/user.commands.ts import { inject, registry, injectable } from "tsyringe" ... @injectable() @registry([ { token: 'UserService', useClass: UserService }, { token: 'UserAdapter', useClass: UserAdapter }, ]) export class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } } container.register("UserCommands", { useClass: UserCommands}) export { container }
兩種方法各有利弊,但歸根結底,這只是個人喜好的問題。
現在我們的容器已經充滿了我們的依賴項,我們可以根據需要使用容器的resolve方法從容器中取得它們。
import { container, UserCommands } from "@/core/user/user.commands" ... const userCommands = container.resolve<UserCommands>("UserCommands") await userCommands.fetchByUUID(uuid) ...
這個例子非常簡單,因為每個類別只依賴另一個類,但我們的服務可能依賴許多類,依賴注入確實有助於保持一切整潔。
但是等等!別就這樣離開我!測試怎麼樣?
我們的注入還可以透過將模擬物件直接發送到我們的依賴項來幫助我們測試程式碼。讓我們來看一個程式碼範例:
import { container, UserCommands } from "@/core/user/user.commands" describe("test ftw", () => { let userAdapterMock: UserAdapterMock let userCommands: UserCommands beforeEach(() => { userAdapterMock = new UserAdapter() container.registerInstance<UserAdapter>("UserAdapter", userAdapter) userCommands = container.resolve<UserCommands>("UserCommands") }); ... });
現在 UserAdapter 令牌包含一個模擬,它將被注入到依賴類別中。
使用標記來命名:不要使用字串文字作為注入標記,而是建立常數標記:
export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
Scoped containers: Use scoped containers for request-scoped dependencies in web applications.
Don't overuse DI: Not everything needs to be injected. Use DI for cross-cutting concerns and configurable dependencies.
If you've come this far, I want to say thank you for reading. I hope you found this article instructive. Remember to always consider the specific needs of your project when implementing dependency injection and architectural patterns.
Likes and comment feedback are the best ways to improve.
Happy coding!
以上がTypeScript での Tsyringe と依存関係の注入の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。