文章目录
什么是装饰器
decorators在TS中是一种可以不修改类代码的基础上通过添加标注的方式来对类型进行扩展的一种方式
- 减少代码量
- 提高代码扩展性、可读性和可维护性
- 只能在类中使用
语法
装饰器的使用极其的简单
- 装饰器本质就是一个函数
- 通过特定语法在特定的位置调用装饰器函数即可对数据(类、方法、甚至参数等)进行扩展
启用装饰器特性
experimentalDecorators: true
// 装饰器函数
function log(target: Function, type: string, descriptor: PropertyDescriptor) {
let value = descriptor.value;
descriptor.value = function(a: number, b: number) {
let result = value(a, b);
console.log('日志:', {
type,
a,
b,
result
})
return result;
}
}
// 原始类
class M {
@log
static add(a: number, b: number) {
return a + b;
}
@log
static sub(a: number, b: number) {
return a - b;
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
装饰器种类
装饰器
是一个函数,它可以通过 @装饰器函数
这种特殊的语法附加在 类
、方法
、访问符
、属性
、参数
上,对它们进行包装,然后返回一个包装后的目标对象(类
、方法
、访问符
、属性
、参数
)
装饰器工作在类的构建阶段,而不是使用阶段
类装饰器
目标
- 应用于类的构造函数
参数
- 第一个参数(也只有一个参数)
target
类的构造函数作为其唯一的参数
属性装饰器
访问器装饰器
目标
- 应用于类的访问器(getter、setter)上
参数
- 第一个参数
- 静态方法:类的构造函数
- 实例方法:类的原型对象
- 第二个参数
- 属性名称
- 第三个参数
- 方法描述符对象
方法装饰器
目标
- 应用于类的方法上
参数
- 第一个参数
target
- 静态方法
static
:类的构造函数 - 实例方法:类的原型对象
- 静态方法
- 第二个参数
name
- 被装饰的方法名称
- 第三个参数
descriptor:PropertyDescriptor
- 方法描述符对象
参数装饰器
目标
- 应用在参数上
参数
- 第一个参数
- 静态方法:类的构造函数
- 实例方法:类的原型对象
- 第二个参数
- 方法名称
- 第三个参数
- 参数在函数参数列表中的索引
实例
function d1(target: Function) {
console.log("---------------d1类装饰器---------------------")
console.log(target)
console.log(typeof target)
}
function d2(target: any, name: string) {
console.log("---------------d2属性装饰器---------------------")
console.log(typeof target, name)
}
function d3(target: any, name: string, descriptor: PropertyDescriptor) {
console.log("---------------d3访问器装饰器---------------------")
console.log(typeof target, name)
console.log(descriptor)
}
function d4(target: any, name: string, descriptor: PropertyDescriptor) {
console.log("---------------d4方法装饰器---------------------")
console.log(typeof target, name)
console.log(descriptor)
}
function d5(target: any, name: string, index: number) {
// name 是当前参数所在的方法
console.log("---------------d5参数装饰器---------------------")
console.log(typeof target, name)
console.log(index)
}
@d1
class MyClass {
@d2
static property1: number;
@d2
a: number
@d3
get b() {
return 1;
}
@d3
static get c() {
return 2;
}
@d4
public method1(@d5 x: number, @d5 y: number) { };
@d4
public static method2() { };
}
执行顺序
---------------d2属性装饰器---------------------
object a
---------------d3访问器装饰器---------------------
object b
{
get: [Function: get],
set: undefined,
enumerable: false,
configurable: true
}
---------------d5参数装饰器---------------------
object method1
1
---------------d5参数装饰器---------------------
object method1
0
---------------d4方法装饰器---------------------
object method1
{
value: [Function],
writable: true,
enumerable: true,
configurable: true
}
---------------d2属性装饰器---------------------
function property1
---------------d3访问器装饰器---------------------
function c
{
get: [Function: get],
set: undefined,
enumerable: false,
configurable: true
}
---------------d4方法装饰器---------------------
function method2
{
value: [Function],
writable: true,
enumerable: true,
configurable: true
}
---------------d1类装饰器---------------------
[Function: MyClass] { method2: [Function] }
function
装饰器执行顺序分析
- 实例装饰器
属性装饰器 => 访问器装饰器 => 参数装饰器 => 方法装饰器 - 静态装饰器
属性 => 访问器 => 参数 => 方法 - 类装饰器
复合装饰器
装饰器工厂
如果我们需要给装饰器执行过程中传入一些参数的时候,就可以使用装饰器工厂来实现
- 通过闭包传参
- 闭包里返回装饰器函数
function log(type: string) {
return function log(target: Function, name: string, descriptor: PropertyDescriptor) {
let value = descriptor.value;
descriptor.value = function (x: number, y: number) {
let result = value(x, y);
console.log({
type,
name,
x,
y,
result
});
return result;
}
}
}
class M {
@log("log")
static add(x: number, y: number) {
return x + y
}
@log("storage")
static sub(x: number, y: number) {
return x - y
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
log
{ type: 'log', name: 'add', x: 1, y: 2, result: 3 }
3
{ type: 'storage', name: 'sub', x: 1, y: 2, result: -1 }
-1
元数据
在 装饰器
函数中 ,我们可以拿到 类
、方法
、访问符
、属性
、参数
的基本信息,如它们的名称,描述符 等
但是我们想获取更多信息就需要通过另外的方式来进行:元数据
function L(type: string) {
return function (target: Function) {
target.prototype.type = type;
}
}
function log(type?: string) {
return function log(target: any, name: string, descriptor: PropertyDescriptor) {
// log 方法装饰器比L类装饰器先执行,想访问L添加到原型链中的内容需要在
let value = descriptor.value;
descriptor.value = function (x: number, y: number) {
let result = value(x, y);
let _type = type;
if (!_type) {
_type = typeof target === "function" ? target.prototype.type : target.type;
}
console.log({
type: _type,
name,
x,
y,
result
});
return result;
}
}
}
@L("storage")
class M {
@log() // 没传参就用类装饰器的参数
static add(x: number, y: number) {
return x + y
}
@log("log") // 传了参就用自己的装饰器参数
static sub(x: number, y: number) {
return x - y
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
什么是元数据?
元数据
:用来描述数据的数据,在我们的程序中,对象
、类
等都是数据,它们描述了某种数据
另外还有一种数据,它可以用来描述 对象
、类
,这些用来描述数据的数据就是 元数据
比如一首歌曲本身就是一组数据,同时还有一组用来描述歌曲的歌手、格式、时长的数据,那么这组数据就是歌曲数据的元数据
使用 reflect-metadata
https://www.npmjs.com/package/reflect-metadata
首先,需要安装 reflect-metadata
npm install reflect-metadata
定义元数据
我们可以 类
、方法
等数据定义元数据
- 元数据会被附加到指定的
类
、方法
等数据之上,但是又不会影响类
、方法
本身的代码
设置
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)
- metadataKey:meta 数据的 key
- metadataValue:meta 数据的 值
- target:meta 数据附加的目标
- propertyKey(可选):对应的 property key
获取
Reflect.getMetadata(metadataKey, target, propertyKey)
参数的含义与 defineMetadata
对应
import "reflect-metadata"
class A {
public static method1() {
}
public method2() {
}
}
let obj = new A;
Reflect.defineMetadata("n", 1, A);
Reflect.defineMetadata("n", 2, A, "method1");
Reflect.defineMetadata("n", 3, obj);
Reflect.defineMetadata("n", 4, A, "method2");
console.log(Reflect.getMetadata("n", A));
console.log(Reflect.getMetadata("n", A, "method1"))
console.log(Reflect.getMetadata("n", obj))
console.log(Reflect.getMetadata("n", obj, "method2"))
1
2
3
4
装饰器简化操作
-
通过
Reflect.defineMetadata
方法调用来添加 元数据 -
通过
@Reflect.metadata
装饰器来添加 元数据
import "reflect-metadata"
@Reflect.metadata("n", 1)
class A {
@Reflect.metadata("n", 2)
public static method1() {
}
@Reflect.metadata("n", 4)
public method2() {
}
}
let obj = new A;
console.log(Reflect.getMetadata("n", A));
console.log(Reflect.getMetadata("n", A, "method1"))
console.log(Reflect.getMetadata("n", obj))
console.log(Reflect.getMetadata("n", obj, "method2"))
1
2
undefined
4
使用元数据的 log 装饰器
import "reflect-metadata";
function L(type: string) {
return function (target: Function) {
Reflect.defineMetadata("type", type, target);
}
}
function log(type?: string) {
return function log(target: any, name: string, descriptor: PropertyDescriptor) {
// log 方法装饰器比L类装饰器先执行,想访问L添加到原型链中的内容需要在
let value = descriptor.value;
descriptor.value = function (x: number, y: number) {
let result = value(x, y);
let _type = type;
if (!_type) {
if (typeof target === "function") {
_type = Reflect.getMetadata("type", target);
} else {
// 实例
_type = Reflect.getMetadata("type", target.constructor)
}
}
console.log({
type: _type,
name,
x,
y,
result
});
return result;
}
}
}
@L("storage")
class M {
@log() // 没传参就用类装饰器的参数
static add(x: number, y: number) {
return x + y
}
@log("log") // 传了参就用自己的装饰器参数
static sub(x: number, y: number) {
return x - y
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
// 原始类
@L('log')
class M {
@log
static add(a: number, b: number) {
return a + b;
}
@log
static sub(a: number, b: number) {
return a - b;
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
优化
import "reflect-metadata";
function log(type?: string) {
return function log(target: any, name: string, descriptor: PropertyDescriptor) {
// log 方法装饰器比L类装饰器先执行,想访问L添加到原型链中的内容需要在
let value = descriptor.value;
descriptor.value = function (x: number, y: number) {
let result = value(x, y);
let _type = type;
if (!_type) {
if (typeof target === "function") {
_type = Reflect.getMetadata("type", target);
} else {
// 实例
_type = Reflect.getMetadata("type", target.constructor)
}
}
console.log({
type: _type,
name,
x,
y,
result
});
return result;
}
}
}
@Reflect.metadata("type", "storage")
class M {
@log() // 没传参就用类装饰器的参数
static add(x: number, y: number) {
return x + y
}
@log("log") // 传了参就用自己的装饰器参数
static sub(x: number, y: number) {
return x - y
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
使用 emitDecoratorMetadata
如何知道一个方法中有多少个参数,每个参数的类型是什么呢?
function f() {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
console.log(descriptor.value.length);
}
}
class B {
name: string;
constructor(a: string) {
}
@f()
method(a: string, b: string): string {
return "a";
}
}
2
在 tsconfig.json
中有一个配置 emitDecoratorMetadata
,开启该特性,typescript
会在编译之后自动给 类
、方法
、访问符
、属性
、参数
添加如下几个元数据
- design:type:被装饰目标的类型
- 成员属性:属性的标注类型
- 成员方法:
Function
类型
- design:paramtypes
- 成员方法:方法形参列表的标注类型
- 类:构造函数形参列表的标注类型
- design:returntype
- 成员方法:函数返回值的标注类型
import "reflect-metadata"
function n(target: any) {
}
function f(name: string) {
return function(target: any, propertyKey: string, descriptor: any) {
console.log( 'design type', Reflect.getMetadata('design:type', target, propertyKey) );
console.log( 'params type', Reflect.getMetadata('design:paramtypes', target, propertyKey) );
console.log( 'return type', Reflect.getMetadata('design:returntype', target, propertyKey) );
}
}
function m(target: any, propertyKey: string) {
}
@n
class B {
@m
name: string;
constructor(a: string) {
}
@f('')
method1(a: string, b: string) {
return 'a'
}
}
编译后
__decorate([
m,
__metadata("design:type", String)
], B.prototype, "name", void 0);
__decorate([
f(''),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], B.prototype, "method1", null);
B = __decorate([
n,
__metadata("design:paramtypes", [String])
], B);
根据标注,返回对应内容
import "reflect-metadata";
function f() {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
let _t = Reflect.getMetadata(
"design:paramtypes",
target,
name
)[0];
console.log(_t);
let value = descriptor.value;
if (_t === Number) {
console.log("标注的是个数字类型");
}
if (_t === String) {
value("lc");
}
if (_t === Date) {
value(new Date())
}
}
}
class B {
name: string;
constructor() {
}
@f()
method(a: string, b: number): string {
return "a";
}
@f()
method2(x?: Date) {
console.log(x)
}
}
let b = new B();
b.method2()
@f()
method2(x?: number) {
console.log(x)
}
class B {
...
@f()
method2(x?: string) {
console.log(x)
}
}