前言
装饰器对我来说可是困惑了不少时间。
每一次看到装饰器代码总有种头大的感觉。
只到最近我终于下定决心啃下它,这颗代码阅读的拦路石,在经过大致阅读我大概学到了一部分,所以做个简单的代码总结。
装饰器的用法其实和代理对象,高阶函数有点相似,都是 hook 行为,只不过装饰器只能在类上使用,而代理对象则是构造一个代理对象,在上面添加代理行为,高阶函数则是通过嵌套调用或者函数管道的形式对函数的输入,输出做了一层 hook。
装饰器作用
类装饰器
可以扩展类、实例、原型的属性方法,通常用作一些隐式扩展。
就目前我所阅读的资料来说,没有一种办法可以显式类的装饰属性和方法。虽然运行时的输出确实是成功装饰了。
属性装饰器 参数装饰器
通常的话,主要是属性的底层性质,比如是否可枚举、可修改等。
get/set access
get / set 相信的大家都不陌生,但是要编写 get set 通常需要大量的样板代码,编写一个内部属性,编写一个 get 读取 一个 set 做设置。 而 access 存取器则更简单
export function cacheAccessorCleanFacory<
// 引入当前Class类型
C extends { new (...args: any[]): {} },
T = any
>(clearnCacheKeys: (keyof InstanceType<C>)[]|[] =[]) {
return function (
traget: any,
propertyKey: string,
describtor: TypedPropertyDescriptor<T>
) {
// 无法通过 target[propertyKey] 报错 私有属性 ,但打印未发现该对象和类以及实例原型有关
const manager = {
v: describtor.value,
};
describtor.get = function (){
return manager.v!;
};
describtor.set = function (this: CacheClass, v) {
if (clearnCacheKeys.length > 0) {
clearnCacheKeys.forEach((key) => this.removeCache(key));
} else {
this.removeCache();
}
manager.v = v;
};
};
}
class God {
@cacheAccessorCleanFacory<typeof God>
access power = '强大的';
}
在 typescript 底层就会自动生成一个样板代码,它的作用也就就方便我们使用装饰器装饰。使用这个关键字段我们就能修改 get / set 也不用写样板代码,虽然只用 set 也可以访问这些东西,但 access 更符合开发直觉。 搭配这个访问设置属性我们以可以优雅的写出副作用,切面代码等。
方法装饰器
非常适用,对于大多数业务需求都是使用这个方法进行 AOP 代码。
- 编写的时候要注意 this 指向问题,这个地方我踩了挺大的坑。
- 建议使用工厂函数,不然代码量直接暴涨,编写大量的修改原生行为代码。
- typescript 类型提示似乎存在一小部分问题,装饰器类型里面有个泛型 T 接收不了参数。
- target 至今没有搞懂是什么对象,似乎是一个空的普通对象,看 Ts 描述似乎是提供一个对象方法的参数.
/** 这个装饰方法重写的类型 */
export type MethodDecoratorFix<T = any> = (
target: Object,
propertyKey: string|symbol,
descriptor: TypedPropertyDescriptor<T>,
) => TypedPropertyDescriptor<T> | void;
const emptryError = <F extends (...args: any[]) => any> = (msg: string): MethodDecoratorFix<F> => {
return function(taget,propertKey, descriptor){
const originMethod = descriptor.value!;
// function 由调用者决定 this 指向
descriptor.value = function(this: any,...args: any[]) {
// this -> 指向实例对象,
// apply 修正 this 指向
const result = originMethod.apply(this, args);
if (!result || isArray(result) && result.length === 0) {
throw Error(msg);
}
return result;
}
}
}
const window = window || {};
const getWindow = (): window|underfind => {
const isError = Math.random() > .5;
if (isError) {
return void 0;
}
return window;
}
class Winer {
@emptryError('window 对象不存在')
getWindow():window{}
}
由上面的例子我们就简单的对常用的空异常返回做了个装饰,在代码中也不必写入大量雷同的非空判定。
结语
好了本次小结就到这里。 有新收获在做做总结。