上一节,我们留了个问题:
但我们知道 类型源于 TS,而真正运行的时候被转换成 JS 是不带有类型的,那框架层「factory」又是如何做类型匹配的呢?
在探讨这个问题之前先来看看什么是装饰器
建议结合官网一起看 https://www.typescriptlang.org/docs/handbook/decorators.html
装饰器首先是一个函数,其次他的用法不同于一般的函数调用而是用
TypeScriptfunction xxx() {} @xxx class A {}
其次这个函数的使用场景有限,仅在用类的场景,并且操作对象是下面其中的内容
若几个场景同时存在,执行顺序如下
There is a well defined order to how decorators applied to various declarations inside of a class are applied:
TypeScript@classDecorator class Point { [x: string]: any @functionDecorator() print() {} @functionDecorator() static staticPrint() {} // 因为我们的 functionDecorator2 ,本身便是装饰器 @functionDecorator2 print2() {} } function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) { console.log("run classDecorator", constructor) return class extends constructor { newProperty = "new property" } } // 生成装饰器的工厂函数 function functionDecorator() { console.log("run functionDecorator") return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { // target 实例方法是实例, 静态方法是类的构造函数 console.log("run functionDecorator", target, propertyKey, descriptor) } } function functionDecorator2(target: any, propertyKey: string, descriptor: PropertyDescriptor) { // target 实例方法是实例, 静态方法是类的构造函数 console.log("run functionDecorator2", target, propertyKey, descriptor) }
CODErun functionDecorator run functionDecorator {} print { value: [Function: print], writable: true, enumerable: false, configurable: true } run functionDecorator2 {} print2 { value: [Function: print2], writable: true, enumerable: false, configurable: true } run functionDecorator run functionDecorator [class Point] staticPrint { value: [Function: staticPrint], writable: true, enumerable: false, configurable: true } run classDecorator [class Point]
⚠️注意:这些行为是在 声明 class 的时候就进行的
https://www.typescriptlang.org/docs/handbook/decorators.html#metadata
TS在编译过程中会去掉原始数据类型相关的信息,将TS文件转换为传统的JS文件以供JS引擎执行。但是,一旦我们引入reflect-metadata并使用装饰器语法对一个类或其上的方法、属性、访问器或方法参数进行了装饰,那么TS在编译后就会自动为我们所装饰的对象增加一些类型相关的元数据
TypeScriptimport "reflect-metadata" function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log(Reflect.getMetadata("design:type", target, propertyKey)) console.log(Reflect.getMetadata("design:paramtypes", target, propertyKey)) console.log(Reflect.getMetadata("design:returntype", target, propertyKey)) } class Point { constructor() {} @validate setX(x: number, y: string): number { return "1" as any } } const p = new Point()
CODE[Function: Function] [ [Function: Number], [Function: String] ] [Function: Number]
可以看到,我们在运行时,拿到了TS环境中的类型,这样就解决了文章开头所提的如何在运行时拿到类型的问题
如果我们想自己设置一些值在 matadata 中可以
TypeScript// 还未成为标准,因此想使用reflect-metadata中的方法就需要手动引入该库,引入后相关方法会自动挂在Reflect全局对象上 import 'reflect-metadata' class Example { text: string } // 定义一个exp接收Example实例,: Example/: string提供给TS编译器进行静态类型检查,不过这些类型信息会在编译后消失 const exp: Example = new Example() // 注意:手动添加元数据仅为展示reflect-metadata的使用方式,实际上大部分情况下应该由编译器在编译时自动添加相关代码 // 为了在运行时也能获取exp的类型,我们手动调用defineMetadata方法为exp添加了一个key为type,value为Example的元数据 Reflect.defineMetadata('type', 'Example', exp) // 为了在运行时也能获取text属性的类型,我们手动调用defineMetadata方法为exp的属性text添加了一个key为type,value为Example的元数据 Reflect.defineMetadata('type', 'String', exp, 'text') // 运行时调用getMetadata方法,传入希望获取的元数据key以及目标就可以得到相关信息(这里得到了exp以及text的类型信息) // 输出'Example' 'String' console.log(Reflect.getMetadata('type', exp)) console.log(Reflect.getMetadata('type', exp, 'text'))
利用 tsc 编译上面的文件
TypeScript"use strict" var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc) else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r return c > 3 && r && Object.defineProperty(target, key, r), r } exports.__esModule = true require("reflect-metadata") function validate(target, propertyKey, descriptor) { console.log(Reflect.getMetadata("design:type", target, propertyKey)) console.log(Reflect.getMetadata("design:paramtypes", target, propertyKey)) console.log(Reflect.getMetadata("design:returntype", target, propertyKey)) } var Point = /** @class */ (function () { function Point() {} Point.prototype.setX = function (x, y) { return "1" } __decorate([validate], Point.prototype, "setX") return Point })() var p = new Point()
看到这里 我们可以理解官网中
As such, the following steps are performed when evaluating multiple decorators on a single declaration in TypeScript:
的含义了 1的部分就是 when composing functions f and g, the resulting composite (f ∘ g)(x) is equivalent to f(g(x)). 2的部分就是
TypeScriptfor (var i = decorators.length - 1; i >= 0; i--) __decorate([validate], Point.prototype, "setX")
这一块更详细的部分可以参考 Nest.js入门 —— TS装饰器与元数据(二) 的第四节