TypeSript 装饰器用法小结

190 阅读3分钟

前言

装饰器对我来说可是困惑了不少时间。

每一次看到装饰器代码总有种头大的感觉。

只到最近我终于下定决心啃下它,这颗代码阅读的拦路石,在经过大致阅读我大概学到了一部分,所以做个简单的代码总结。 装饰器的用法其实和代理对象,高阶函数有点相似,都是 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 代码。

  1. 编写的时候要注意 this 指向问题,这个地方我踩了挺大的坑。
  2. 建议使用工厂函数,不然代码量直接暴涨,编写大量的修改原生行为代码。
  3. typescript 类型提示似乎存在一小部分问题,装饰器类型里面有个泛型 T 接收不了参数。
  4. 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{}
}

由上面的例子我们就简单的对常用的空异常返回做了个装饰,在代码中也不必写入大量雷同的非空判定。

结语

好了本次小结就到这里。 有新收获在做做总结。