”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > TypeScript 中的 TSyringe 和依赖注入

TypeScript 中的 TSyringe 和依赖注入

发布于2024-10-31
浏览:837

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.resolve("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.registerInstance("UserAdapter", userAdapter)
        userCommands = container.resolve("UserCommands")
    });

    ...
});

现在 UserAdapter 令牌包含一个将被注入到依赖类中的模拟。

最佳实践和技巧

  1. 使用接口:为您的依赖项定义接口,使它们易于交换和测试。为了简单起见,我在本文中没有使用接口,但接口就是生命。
  2. 避免循环依赖:构建代码以避免循环依赖,这可能会导致 TSyringe 出现问题。
  3. 使用标记进行命名:不使用字符串文字作为注入标记,而是创建常量标记:

    export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
    
    
  4. 作用域容器:将作用域容器用于 Web 应用程序中的请求作用域依赖项。

  5. 不要过度使用 DI:并不是所有东西都需要注入。使用 DI 来实现横切关注点和可配置的依赖关系。

如果您已经读到这里,我想说谢谢您的阅读。我希望这篇文章对您有所启发。请记住在实现依赖注入和架构模式时始终考虑项目的特定需求。

点赞和评论反馈是改进的最佳方式。

编码愉快!

版本声明 本文转载于:https://dev.to/gdsources/tsyringe-and-dependency-injection-in-typescript-3i67?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Java为何无法创建泛型数组?
    Java为何无法创建泛型数组?
    通用阵列创建错误 arrayList [2]; JAVA报告了“通用数组创建”错误。为什么不允许这样做?答案:Create an Auxiliary Class:public static ArrayList<myObject>[] a = new ArrayList<myO...
    编程 发布于2025-07-12
  • 如何高效地在一个事务中插入数据到多个MySQL表?
    如何高效地在一个事务中插入数据到多个MySQL表?
    mySQL插入到多个表中,该数据可能会产生意外的结果。虽然似乎有多个查询可以解决问题,但将从用户表的自动信息ID与配置文件表的手动用户ID相关联提出了挑战。使用Transactions和last_insert_id() 插入用户(用户名,密码)值('test','test...
    编程 发布于2025-07-12
  • 如何干净地删除匿名JavaScript事件处理程序?
    如何干净地删除匿名JavaScript事件处理程序?
    删除匿名事件侦听器将匿名事件侦听器添加到元素中会提供灵活性和简单性,但是当要删除它们时,可以构成挑战,而无需替换元素本身就可以替换一个问题。 element? element.addeventlistener(event,function(){/在这里工作/},false); 要解决此问题,请考虑...
    编程 发布于2025-07-12
  • 在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    For Each Loop vs. Iterator: Efficiency in Collection TraversalIntroductionWhen traversing a collection in Java, the choice arises between using a for-...
    编程 发布于2025-07-12
  • 为什么我的CSS背景图像出现?
    为什么我的CSS背景图像出现?
    故障排除:CSS背景图像未出现 ,您的背景图像尽管遵循教程说明,但您的背景图像仍未加载。图像和样式表位于相同的目录中,但背景仍然是空白的白色帆布。而不是不弃用的,您已经使用了CSS样式: bockent {背景:封闭图像文件名:背景图:url(nickcage.jpg); 如果您的html,css...
    编程 发布于2025-07-12
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-07-12
  • 将图片浮动到底部右侧并环绕文字的技巧
    将图片浮动到底部右侧并环绕文字的技巧
    在Web设计中围绕在Web设计中,有时可以将图像浮动到页面右下角,从而使文本围绕它缠绕。这可以在有效地展示图像的同时创建一个吸引人的视觉效果。 css位置在右下角,使用css float and clear properties: img { 浮点:对; ...
    编程 发布于2025-07-12
  • 如何正确使用与PDO参数的查询一样?
    如何正确使用与PDO参数的查询一样?
    在pdo 中使用类似QUERIES在PDO中的Queries时,您可能会遇到类似疑问中描述的问题:此查询也可能不会返回结果,即使$ var1和$ var2包含有效的搜索词。错误在于不正确包含%符号。通过将变量包含在$ params数组中的%符号中,您确保将%字符正确替换到查询中。没有此修改,PDO...
    编程 发布于2025-07-12
  • 如何在其容器中为DIV创建平滑的左右CSS动画?
    如何在其容器中为DIV创建平滑的左右CSS动画?
    通用CSS动画,用于左右运动 ,我们将探索创建一个通用的CSS动画,以向左和右移动DIV,从而到达其容器的边缘。该动画可以应用于具有绝对定位的任何div,无论其未知长度如何。问题:使用左直接导致瞬时消失 更加流畅的解决方案:混合转换和左 [并实现平稳的,线性的运动,我们介绍了线性的转换。这...
    编程 发布于2025-07-12
  • 图片在Chrome中为何仍有边框?`border: none;`无效解决方案
    图片在Chrome中为何仍有边框?`border: none;`无效解决方案
    在chrome 中删除一个频繁的问题时,在与Chrome and IE9中的图像一起工作时,遇到了一个频繁的问题。和“边境:无;”在CSS中。要解决此问题,请考虑以下方法: Chrome具有忽略“ border:none; none;”的已知错误,风格。要解决此问题,请使用以下CSS ID块创建带...
    编程 发布于2025-07-12
  • 为什么不使用CSS`content'属性显示图像?
    为什么不使用CSS`content'属性显示图像?
    在Firefox extemers属性为某些图像很大,&& && && &&华倍华倍[华氏华倍华氏度]很少见,却是某些浏览属性很少,尤其是特定于Firefox的某些浏览器未能在使用内容属性引用时未能显示图像的情况。这可以在提供的CSS类中看到:。googlepic { 内容:url(&#...
    编程 发布于2025-07-12
  • 解决Spring Security 4.1及以上版本CORS问题指南
    解决Spring Security 4.1及以上版本CORS问题指南
    弹簧安全性cors filter:故障排除常见问题 在将Spring Security集成到现有项目中时,您可能会遇到与CORS相关的错误,如果像“访问Control-allo-allow-Origin”之类的标头,则无法设置在响应中。为了解决此问题,您可以实现自定义过滤器,例如代码段中的MyFi...
    编程 发布于2025-07-12
  • 如何避免Go语言切片时的内存泄漏?
    如何避免Go语言切片时的内存泄漏?
    ,a [j:] ...虽然通常有效,但如果使用指针,可能会导致内存泄漏。这是因为原始的备份阵列保持完整,这意味着新切片外部指针引用的任何对象仍然可能占据内存。 copy(a [i:] 对于k,n:= len(a)-j i,len(a); k
    编程 发布于2025-07-12
  • 对象拟合:IE和Edge中的封面失败,如何修复?
    对象拟合:IE和Edge中的封面失败,如何修复?
    To resolve this issue, we employ a clever CSS solution that solves the problem:position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%)...
    编程 发布于2025-07-12
  • FastAPI自定义404页面创建指南
    FastAPI自定义404页面创建指南
    response = await call_next(request) if response.status_code == 404: return RedirectResponse("https://fastapi.tiangolo.com") else: ...
    编程 发布于2025-07-12

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3