前言
- 本篇梳理ts装饰器,装饰器很多文章里都有讲,不过很多文章少讲漏讲现象很多,先总结第一波,因为我暂时没理解透,结合nest还有很多地方有点问题,如果照nest写法,有些实现应该报错才对。这篇差点把我写吐了,一开始还想写,写后面越来越不想写。
聊一下JS装饰器
- 为了有更好的学习效果,我们可以比较一下js装饰器,实际上,js装饰器和ts装饰器并不兼容,而且js装饰器曾有过大变动,这些在我百度的资料里都没有找到,都是我去github上翻到的。
- js装饰器曾经有巨大改版,主要是在2个方面:
- 第一个方面是多个装饰器在装饰类的原型方法时,执行顺序不同:
function dec(id){
console.log('evaluated', id);
return (target, property, descriptor) => console.log('executed', id);
}
class Example {
@dec(1)
@dec(2)
method(){}
}
evaluated 2
evaluated 1
executed 2
executed 1
evaluated 1
evaluated 2
executed 2
executed 1
- 第二个方面在于修饰类的静态属性时,老版本的descriptor没有initializer缓存值:
function dec(target, prop, descriptor){
let {initializer} = descriptor;
delete descriptor.initializer;
delete descriptor.writable;
descriptor.get = function(){
return initializer.call(this);
};
}
class Example {
@dec
static prop = i++;
}
- 另外ts装饰器和js装饰器有个最大最明显的区别就是,ts装饰器的参数必须写全,否则报错,这是一个很奇怪的现象,相反,js装饰器就没这要求。而ts装饰器在修饰不同东西时装饰器的参数是不同的,参数数量也是不同的,所以这就导致ts装饰器上手比js装饰器难得多,并不是一个简单的语法糖。
- 而js装饰器参数不用写全就可以使用,可以看一下babel对于js装饰器的写法:
- babel地址
- 由于js装饰器已经没落了,所以可以不用学了,直接学下ts装饰器,ts装饰器语法很稳定,而且已经逐渐被大量使用了。
装饰器类型
- 装饰器有4种,类装饰器、属性装饰器、方法装饰器和参数装饰器。
类装饰器
- 这个类装饰器,有很多说头,因为使用花样挺多的,为了方便学习,
- 第一种方式,替换类的原型的方法与属性:
- 接收参数为类的原型对象
function addNameEat(constructor: typeof Person) {
constructor.prototype.name = "yehuozhili";
constructor.prototype.eat = function () {
console.log("eat");
};
}
@addNameEat
class Person {
name: string;
eat: Function;
constructor() {}
}
let p: Person = new Person();
console.log(p.name);
p.eat();
- 注意 ! addNameEat并不是返回一个函数,所以装饰器后面没有执行的括号。
- 第二种方式,返回函数进行执行,是第一种方法的传参版本:
function addNameEat(name:string) {
return function (constructor: typeof Person) {
constructor.prototype.name = name;
constructor.prototype.eat = function () {
console.log("eat");
};
};
}
@addNameEat('yehuozhili')
class Person {
name: string;
eat: Function;
constructor() {}
}
let p: Person = new Person();
console.log(p.name);
p.eat();
- 第三种方式,返回个类替换原来的 ,替换的类有要求,必须要实现原有类的原型方法:
interface PersonExtends extends Person{
bbb:number
hello:Function
}
function addNameEat(name: string) {
return function (_constructor: typeof Person) {
return class {
name: string;
eat() {
console.log("修改后");
}
hello() {
console.log("new function", name);
}
static aaa: string = "1";
static bbb: number = 111;
};
};
}
@addNameEat("yehuozhili")
class Person {
name: string;
eat: Function;
constructor() {}
static aaa: string = "222";
}
let p = <PersonExtends>new Person();
console.log(p.name);
p.eat();
p.hello();
let k = <PersonExtends><unknown>Person
console.log(Person.aaa);
console.log(k.bbb)
- 这么做感觉像反向继承的意思,新出来的实例获得了新的方法,新的属性。
- 输入
console.log(p instanceof Person)
的时候,得到的结果也是true。 - 直接增加这个方式少见,一般是使用Object.defineProperty或者Reflect.meta定义后直接返回:
function addNameEat(name:string) {
return function (constructor: typeof Person) {
return constructor
};
}
@addNameEat('yehuozhili')
class Person {
name: string;
eat: Function;
constructor() {}
}
let p: Person = new Person();
console.log(p.name);
p.eat();
- nest的类装饰器也是这种方式,但是暂时没懂如何在类中constructor里传参private不赋值并且不会报错的。
属性装饰器 与 方法装饰器
function upperCase(target: any, propertyKey: string) {
let value = target[propertyKey];
const getter = function () {
return value;
};
const setter = function (newVal: string) {
value = newVal.toUpperCase();
};
if (delete target[propertyKey]) {
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
}
- 在修饰属性或者方法时,target为类的实例,propertyKey为修饰的那个属性,这里不接收propertyKey直接报错:
作为表达式调用时,无法解析属性修饰器的签名
,同理,多接收参数也不行。 - 属性装饰器同时也可以装饰静态属性:
@test
public static age: number = 10;
- 此时target为类,第二个参数为age
- 装饰类的原型方法:
function noEnumerable(
target: any,
_property: string,
descriptor: PropertyDescriptor
) {
console.log("target.getName", target.getName);
console.log("target.getAge", target.getAge);
descriptor.enumerable = true;
}
class Person {
@upperCase
name: string = "yehuozhili";
public static age: number = 10;
constructor() {}
@noEnumerable
getName() {
console.log(this.name);
}
@toNumber
sum(...args: any[]) {
return args.reduce((accu: number, item: number) => accu + item, 0);
}
}
- 实例方法上的装饰器会比属性装饰器多个属性。target为实例,第二个为方法名,最后那个为描述器。
- 装饰类的静态方法,同理,也是3属性,target则变为类。这个例子里第二个参数为aa,就是方法名。
@test
static get aa() {
return "ss";
}
参数装饰器
- 方法与属性装饰器不写第二个参数就报错,而参数装饰器更狠,第三个参数不接收直接报错。
- 套路和上面一样:
interface Person {
age: number;
}
function addAge(target: any, methodName: string, paramsIndex: number) {
console.log(target);
console.log(methodName);
console.log(paramsIndex);
target.age = 10;
}
class Person {
login(username: string, @addAge password: string) {
console.log(this.age, username, password);
}
static aaa(@addAge a:string){
console.log(a)
}
}
let p = new Person();
p.login('yehuozhili', '123456')
- 静态方法的参数target则为类,原型方法的参数target则为类的原型。
- 第二个参数就是方法名
- 最后那个参数是该参数在此方法的索引,这个传索引过来有点引人深思。