Typescript: Nest.js 中使用声明文件定义依赖注入的类型

Nest.js 是一个流行的 Node.js 服务端框架,它基于 TypeScript 构建,并采用了依赖注入(Dependency Injection, DI)的设计模式。在 Nest.js 中,我们可以使用声明文件(declaration files)来定义依赖注入的类型,从而获得更好的类型检查和智能提示。本文将从浅入深,结合 Nest.js 的源码,讲解如何使用声明文件定义依赖注入的类型。

Nest.js 中的依赖注入

在深入探讨声明文件之前,我们先来了解一下 Nest.js 中的依赖注入。在 Nest.js 中,依赖注入是通过 @Injectable 装饰器实现的。当一个类被 @Injectable 装饰时,Nest.js 会自动创建该类的实例,并将其注入到需要该类实例的地方。

下面是一个简单的例子:

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  getUser(id: number) {
    // ...
  }
}

@Injectable()
export class AuthService {
  constructor(private readonly userService: UserService) {}

  async validateUser(id: number) {
    const user = await this.userService.getUser(id);
    // ...
  }
}

在这个例子中,UserServiceAuthService 都是可注入的类。AuthService 的构造函数中声明了一个 UserService 类型的依赖,Nest.js 会自动创建 UserService 的实例,并将其注入到 AuthService 中。

在 Nest.js 源码中探索声明文件的使用

那么,Nest.js 内部是如何使用声明文件来定义依赖注入的类型呢?让我们一起探索一下 Nest.js 的源码。

在 Nest.js 的源码中,有一个 injectable.ts 文件,它定义了 @Injectable 装饰器:

export function Injectable(): ClassDecorator {
  return (target: Function) => {
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, { scope: Scope.DEFAULT }, target);
  };
}

这里使用了 Reflect.defineMetadata 来为类添加元数据,标记该类是可注入的。SCOPE_OPTIONS_METADATA 是一个常量,用于定义元数据的键名。

injector.ts 文件中,定义了 Injector 类,它负责创建和管理可注入类的实例:

export class Injector {
  // ...

  public get<T>(typeOrToken: Type<T> | Abstract<T> | string | symbol, options: InjectOptions = {}): T {
    const instanceKey = this.getInstanceKey(typeOrToken, options);
    const instanceWrapper = this.instances.get(instanceKey);
    if (instanceWrapper) {
      return instanceWrapper.instance;
    }

    // ...

    const instance = this.instantiateClass<T>(instanceWrapper, moduleRef, ctorHost);
    this.instances.set(instanceKey, new InstanceWrapper(instance));
    return instance;
  }

  // ...
}

这里的 get 方法用于获取可注入类的实例。它首先根据类型或令牌(token)获取实例的键名,然后从 instances 映射中获取实例。如果实例不存在,就调用 instantiateClass 方法创建实例,并将其存储到 instances 映射中。

instantiateClass 方法的定义如下:

private instantiateClass<T>(
  instanceWrapper: InstanceWrapper<T>,
  moduleRef: Module,
  ctorHost: Type<any> | undefined,
): T {
  const { metatype, inject } = instanceWrapper;

  // ...

  const instance = new metatype(...deps);
  instanceWrapper.instance = instance;
  return instance;
}

这里使用了 new 关键字来创建类的实例,并将依赖项作为构造函数的参数传入。metatype 表示要实例化的类,inject 表示类的依赖项。

声明文件在 Nest.js 依赖注入中的应用

为了更好地理解声明文件在 Nest.js 依赖注入中的应用,我们以一个实际的例子来说明。假设我们有一个 UserModule,它包含了 UserServiceAuthService:

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { AuthService } from './auth.service';

@Module({
  providers: [UserService, AuthService],
  exports: [UserService],
})
export class UserModule {}

这里使用 @Module 装饰器定义了 UserModule,并在 providers 属性中声明了 UserServiceAuthService

现在,我们要在其他模块中使用 UserService,可以像下面这样导入 UserModule:

import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { PostService } from './post.service';

@Module({
  imports: [UserModule],
  providers: [PostService],
})
export class PostModule {}

PostModule 中,我们导入了 UserModule,这样就可以在 PostService 中注入 UserService 了:

import { Injectable } from '@nestjs/common';
import { UserService } from './user/user.service';

@Injectable()
export class PostService {
  constructor(private readonly userService: UserService) {}

  createPost(userId: number, content: string) {
    const user = this.userService.getUser(userId);
    // ...
  }
}

这里的关键在于,PostService 是如何知道 UserService 的类型的呢?答案就在于 Nest.js 使用了声明文件来定义依赖注入的类型。

@nestjs/common 包的根目录下,有一个 index.d.ts 文件,它定义了 Nest.js 中常用的类型:

// @nestjs/common/index.d.ts

export * from './decorators';
export * from './enums';
export * from './exceptions';
export * from './http';
export * from './interfaces';
export * from './pipes';
export * from './services';
export * from './utils';

这里使用 export * from 语法导出了各个子目录下的类型定义。其中,./services 目录下的 index.d.ts 文件定义了 Injectable 类型:

// @nestjs/common/services/index.d.ts

export declare type Injectable<T = any> = T;

我们可以用类图来表示这个过程中的关系:

从图中可以看出,UserServiceAuthServicePostService 都是 Injectable 类型的子类型。PostService 依赖于 UserService,这种依赖关系是通过声明文件中的类型定义来实现的。

总结

本文结合 Nest.js 的源码,讲解了如何使用声明文件定义依赖注入的类型。我们首先了解了 Nest.js 中的依赖注入,然后深入 Nest.js 源码,探索了 @Injectable 装饰器和 Injector 类的实现。接着,我们通过一个实际的例子,说明了声明文件在 Nest.js 依赖注入中的应用。

Read more

ngrok本地调试原理及Telegram mini app cookie path 问题

ngrok本地调试原理及Telegram mini app cookie path 问题

在现代web开发中,本地调试是一个非常重要的环节。然而,当我们需要将本地开发的应用暴露到公网以便进行测试时,就会遇到一些挑战。本文将详细介绍如何使用ngrok实现内网穿透进行本地调试,特别是在Telegram小程序开发场景中的应用,以及可能遇到的常见问题及其解决方案。 ngrok原理 ngrok是一个反向代理工具,它可以将本地服务器安全地暴露到公网。下面是ngrok的工作原理: 1. 用户启动ngrok客户端,并指定要暴露的本地端口。 2. ngrok客户端与ngrok云服务建立安全的通道。 3. ngrok云服务生成一个公网可访问的URL。 4. 当外部请求到达这个URL时,ngrok云服务将请求通过安全通道转发到本地ngrok客户端。 5. 本地ngrok客户端将请求转发到指定的本地端口。 6. 本地服务器处理请求并返回响应,响应通过相同的路径返回给客户端。 Telegram小程序调试场景 在Telegram小程序开发中,我们经常需要使用ngrok来进行本地调试。以下是具体步骤: 1. 启动本地开发服务器(例如运行在localhost:3000)。

TypeScript:从架构分层设计到IOC和AOP

TypeScript:从架构分层设计到IOC和AOP

TypeScript作为JavaScript的超集,为开发者提供了强大的类型系统和面向对象编程能力。然而,要在大型项目中充分发挥TypeScript的优势,我们需要深入理解软件架构原则和设计模式。本文将探讨如何使用TypeScript构建一个健壮的应用架构,涵盖分层设计、常见设计模式、控制反转(IOC)和面向切面编程(AOP)等高级概念。 分层架构 分层架构是组织大型应用程序的常用方法。它有助于关注点分离,使得每一层都可以独立开发和测试。一个典型的分层架构包括: 1. 表现层(Presentation Layer) 2. 业务逻辑层(Business Logic Layer) 3. 数据访问层(Data Access Layer) 4. 数据库(Database) 让我们使用图表来可视化这个架构: 接下来,我们将探讨每一层中可能使用的设计模式,并通过TypeScript代码示例来说明如何实现这些模式。 表现层 表现层负责处理用户界面和用户交互。在这一层,我们经常使用MVC(Model-View-Controller)或MVVM(Model-View-ViewM

Jotai v2: React状态管理的新篇章

Jotai v2: React状态管理的新篇章

Jotai是一个为React应用设计的轻量级状态管理库。2023年3月,Jotai发布了v2.0版本,带来了许多新特性和改进。本文将深入探讨Jotai v2的使用方法、适用场景、设计理念、源码结构以及核心功能的实现原理。 版本信息 本文讨论的是Jotai v2.0.3版本,发布于2023年5月。你可以通过以下命令安装 npm install [email protected] 基本使用 Jotai的核心概念是"atom"。atom是最小的状态单位,可以存储任何JavaScript值。让我们看一个简单的例子: import { atom, useAtom } from 'jotai' // 创建一个atom const countAtom = atom(0) function Counter() { // 使用atom const [count, setCount] = useAtom(

加密货币交易所十二:安全性和风险控制

加密货币交易所十二:安全性和风险控制

在加密货币合约交易所中,安全性和风险控制是至关重要的。这不仅关系到交易所的声誉和用户的资产安全,也直接影响到整个加密货币生态系统的稳定性。本章将详细探讨合约交易所在安全性和风险控制方面的关键策略和实施方法。 多重签名机制 多重签名(MultiSig)是一种强大的安全机制,要求多个私钥来授权交易,大大降低了单点故障和内部欺诈的风险。 概念解释 多重签名是一种需要多个私钥来签署和授权交易的加密技术。例如,在一个 2-of-3 多重签名设置中,需要三个私钥中的任意两个来完成交易。 在合约交易所中的应用 热钱包管理: * 设置:通常采用 2-of-3 或 3-of-5 的多重签名方案。 * 应用:每次从热钱包转出大额资金时,需要多个管理员的授权。 冷钱包管理: * 设置:可能采用更严格的 3-of-5 或 4-of-7 方案。 * 应用:定期将热钱包中的多余资金转移到冷钱包时使用。 智能合约升级: * 设置:可能需要多个核心开发者和安全审计员的签名。 * 应用:在升级关键智能合约时,确保变更经过充分审核和授权。 实现考虑 密钥管理: * 使用硬件安全