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);
// ...
}
}
在这个例子中,UserService
和 AuthService
都是可注入的类。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
,它包含了 UserService
和 AuthService
:
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
属性中声明了 UserService
和 AuthService
。
现在,我们要在其他模块中使用 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;
我们可以用类图来表示这个过程中的关系:
从图中可以看出,UserService
、AuthService
和 PostService
都是 Injectable
类型的子类型。PostService
依赖于 UserService
,这种依赖关系是通过声明文件中的类型定义来实现的。
总结
本文结合 Nest.js 的源码,讲解了如何使用声明文件定义依赖注入的类型。我们首先了解了 Nest.js 中的依赖注入,然后深入 Nest.js 源码,探索了 @Injectable
装饰器和 Injector
类的实现。接着,我们通过一个实际的例子,说明了声明文件在 Nest.js 依赖注入中的应用。