装饰器声明后,启动 Nest 应用,对象会创建好,依赖也给注入了,为啥 ?
先看 Reflect 的 metadata
Reflect.get 是获取对象属性值
Reflect.set 是设置对象属性值
Reflect.has 是判断对象属性是否存在
Reflect.apply 是调用某个方法,传入对象和参数
Reflect.construct 是用构造器创建对象实例,传入构造器参数
Nest 用到的 api
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);
Reflect.defineMetadata 和 Reflect.getMetadata 分别用于设置和获取某个类的元数据,如果最后传入了属性名,还可以单独为某个属性设置元数据
那元数据存在哪?
存在类或者对象上,如果给类或者类的静态属性添加元数据,那就保存在类上
如果给实例属性添加元数据,那就保存在对象上,用类似 [[metadata]] 的 key 来存
支持装饰器的方式使用:
@Reflect.metadata(metadataKey, metadataValue)
class C {
@Reflect.metadata(metadataKey, metadataValue)
method() {
}
}
基础封装
function Meta(metadataKey: string, metadataValue: any) {
return Reflect.metadata(metadataKey, metadataValue)
}
实际例子
function Validate() {
return function (target, propertyKey, descriptor) {
const types = Reflect.getMetadata(
'design:paramtypes',
target,
propertyKey
)
const original = descriptor.value
descriptor.value = function (...args) {
args.forEach((arg, index) => {
const Type = types[index]
if (!(arg instanceof Type) && typeof arg !== Type.name.toLowerCase()) {
throw new Error(
`参数 ${index} 类型错误,期望 ${Type.name}`
)
}
})
return original.apply(this, args)
}
}
}
使用
class MathService {
@Validate()
add(a: number, b: number) {
return a + b
}
}
new MathService().add(1, 2) // ✅
new MathService().add('1', 2) // ❌
Nest 的实现原理就是通过装饰器给 class 或者对象添加元数据,然后初始化的时候取出这些元数据,进行依赖的分析,然后创建对应的实例对象就可以了。
所以说,nest 实现的核心就是 Reflect metadata 的 api
安装依赖 & 打开配置
npm i reflect-metadata
tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
入口文件最顶部:
import 'reflect-metadata'
自己写一个装饰器
function MyController(path: string): ClassDecorator {
return (target) => {
Reflect.defineMetadata('path', path, target)
}
}
这一行是核心:
Reflect.defineMetadata(key, value, target)
👆 把
path这个信息,挂到 class 上
使用这个装饰器
@MyController('/user')
class UserController {}
运行时把元数据读出来
const path = Reflect.getMetadata('path', UserController)
console.log(path) // /user
🔑 到这里你已经实现了 Nest 的第一步
Nest 的 Controller / Injectable 本质
Controller 装饰器 ≈
@Controller('/user')
class UserController {}
等价于:
Reflect.defineMetadata('path', '/user', UserController)
Injectable 装饰器 ≈
@Injectable()
class UserService {}
等价于:
Reflect.defineMetadata('injectable', true, UserService)
依赖注入是怎么“自动知道”的?
关键:构造函数参数的类型元数据
@Injectable()
class UserService {}
@Injectable()
class UserController {
constructor(private userService: UserService) {}
}
TypeScript 在背后偷偷干了什么?
开启 emitDecoratorMetadata 后:
Reflect.getMetadata('design:paramtypes', UserController)
得到:
[ UserService ]
💥 这一步是 Nest DI 的灵魂
import 'reflect-metadata'
@Injectable()
class UserService {}
@Injectable()
class UserController {
constructor(private userService: UserService) {}
}
const deps = Reflect.getMetadata(
'design:paramtypes',
UserController
)
console.log(deps)
// [ [class UserService] ]
Nest 初始化时到底在干嘛?
可以理解成这几步👇
function bootstrap() {
// 1. 扫描所有 class
// 2. 找 @Controller / @Injectable
// 3. 读取元数据
// 4. 分析依赖
// 5. new 实例
}
极简 DI 容器
const container = new Map()
function createInstance(target) {
const deps =
Reflect.getMetadata('design:paramtypes', target) || []
const params = deps.map(dep => {
if (!container.has(dep)) {
container.set(dep, createInstance(dep))
}
return container.get(dep)
})
return new target(...params)
}
使用:
const controller = createInstance(UserController)
👉 这就是 Nest DI 的雏形