首頁  >  文章  >  web前端  >  TypeScript 中的 TSyringe 和依賴注入

TypeScript 中的 TSyringe 和依賴注入

Patricia Arquette
Patricia Arquette原創
2024-09-25 19:21:42592瀏覽

TSyringe and Dependency Injection in TypeScript

我不太喜歡像 NestJS 這樣的大型框架;我一直喜歡以我想要的方式構建我的軟體的自由,以及我以輕量級方式決定的結構。但在測試 NestJS 時我喜歡的是依賴注入。

依賴注入(DI)是一種設計模式,它允許我們透過消除創建和管理類別依賴關係的責任來開發鬆散耦合的程式碼。這種模式對於編寫可維護、可測試和可擴展的應用程式至關重要。在 TypeScript 生態系統中,TSyringe 作為一個強大且輕量級的依賴注入容器脫穎而出,它簡化了這個過程。

TSyringe 是一個用於 TypeScript/JavaScript 應用程式的輕量級依賴注入容器。由 Microsoft 在其 GitHub (https://github.com/microsoft/tsyringe) 上維護,它使用裝飾器進行建構函式註入。然後,它使用控制反轉容器來儲存基於令牌的依賴項,您可以用該令牌交換實例或值。

了解依賴注入

在深入了解 TSyringe 之前,我們先簡單探討一下什麼是依賴注入以及它為何如此重要。

依賴注入是一種技術,物件從外部來源接收其依賴項,而不是自己創建它們。這種方法有幾個好處:

  1. 提高了可測試性:可以在單元測試中輕鬆模擬或存根依賴項。
  2. 增加模組化:組件更加獨立,可以輕鬆替換或更新。
  3. 更好的程式碼可重用性:可以在應用程式的不同部分之間共享依賴關係。
  4. 增強的可維護性:依賴項的變更對依賴程式碼的影響最小。

設定 TSyringe

首先,讓我們在您的 TypeScript 專案中設定 TSyringe:

npm install tsyringe reflect-metadata

在 tsconfig.json 中,確保有以下選項:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

在應用程式的入口點匯入反射元資料:

import "reflect-metadata";

應用程式的入口點例如是 Next.js 13+ 上的根佈局,也可以是小型 Express 應用程式中的主檔案。

使用 TSyringe 實作依賴注入

我們以介紹中的範例為例,加入 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.resolve501e5c9e9a27b386ad32ee31a4940f45("UserCommands")
await userCommands.fetchByUUID(uuid)
...

這個例子非常簡單,因為每個類別只依賴另一個類,但我們的服務可能依賴許多類,依賴注入確實有助於保持一切整潔。

但是等等!別就這樣離開我!測試怎麼樣?

使用 TSyringe 進行測試

我們的注入還可以透過將模擬物件直接發送到我們的依賴項來幫助我們測試程式碼。讓我們來看一個程式碼範例:

import { container, UserCommands } from "@/core/user/user.commands"

describe("test ftw", () => {
    let userAdapterMock: UserAdapterMock
    let userCommands: UserCommands

    beforeEach(() => {
        userAdapterMock = new UserAdapter()
        container.registerInstancefb4b20c1f3a4a2a969c7b254c7ec9c29("UserAdapter", userAdapter)
        userCommands = container.resolve501e5c9e9a27b386ad32ee31a4940f45("UserCommands")
    });

    ...
});

現在 UserAdapter 令牌包含一個模擬,它將被注入到依賴類別中。

最佳實踐和技巧

  1. 使用介面:為您的依賴項定義接口,使它們可以輕鬆交換和測試。為了簡單起見,我在本文中沒有使用接口,但接口就是生命。
  2. 避免循環依賴:建構程式碼以避免循環依賴,這可能會導致 TSyringe 出現問題。
  3. 使用標記來命名:不要使用字串文字作為注入標記,而是建立常數標記:

    export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
    
    
  4. Scoped containers: Use scoped containers for request-scoped dependencies in web applications.

  5. 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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn