装饰器的探索之路

221 阅读3分钟

什么是装饰器

  • 装饰器是一种特殊的语法,允许开发者在类声明、方法、属性或参数上添加注解或修改其行为。这种功能提供了一种方式来实现元编程,使得代码更具可读性和可维护性
  • 简单总结来说就是
    • 持有被装饰对象
    • 增强被装饰对象

装饰器类型

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 访问器装饰器
  • 参数装饰器

执行顺序:类装饰器 → 属性装饰器 → 方法装饰器 → 访问器装饰器 → 参数装饰器。

装饰器写法

  • 普通装饰器(无法传参)
  • 装饰器工厂(可以传参)

类装饰器

  • 用于类的构造函数,可以用于修改类的行为或添加元数据。
    • 接收一个参数,构造函数

普通装饰器

const decorator:ClassDecorator = (target:any) => {
    console.log(target===service,target)
    target.prototype.name='bang'
}

//等同于decorator(s)
@decorator
class service{
    constructor() { }
    getName(){}
}

const s:any = new service()
console.log(s.name)

image.png

  • 但是上述例子无法接受传参

image.png

那么如何传参呢,那就是装饰器工厂

装饰器工厂

  • 装饰器工厂很简单,是一个函数的同时,也返回一个函数。也就是函数柯里化
    • 第一个参数是用户自定义的参数
    • 第二个参数接收装饰器本来的那些参数
//params作为用户自定义的参数,其实装饰器工厂就是包了一层
const decorator = (params: string) => {
   //target为装饰器本来的参数
  return (target: any) => {
    console.log(target === service, target, params);
    target.prototype.name = "bang";
  };
};

@decorator("test")
class service {
  constructor() {}
  getName() {}
}

const s: any = new service();
console.log(s.name);

输出结果
true [class service] test
bang

image.png

下面普通装饰器和装饰器工厂直接在代码里举例,不具体划分了

属性装饰器

  • 用于类的属性,可以用于添加元数据。
  • 接收两个参数
    • 一个是原型对象
    • 一个是属性名称
//普通装饰器
const test: PropertyDecorator = (target: any, propertyKey: string | symbol) => {
  console.log(target, propertyKey);
};

class Service {
  @test
  public name: string;
  constructor() {
    this.name = "bang";
  }
}

输出结果
target:{}  propertyKey:name
为什么target是{},而不是{name:'bang'}呢
因为name是实例属性,不是原型属性

image.png

//装饰器工厂
const test = (params: string) => {
  return (target: any, propertyKey: string | symbol) => {
    console.log(target, propertyKey, params);
  };
};

class Service {
  @test("ces")
  public name: string;
  constructor() {
    this.name = "bang";
  }
}
输出结果:
{} name ces

image.png

方法装饰器

  • 用于类的方法,可以用于修改方法的定义或者添加一些额外的逻辑。
  • 接收三个参数
    • 原型对象
    • 方法名称
    • 函数对象描述符
      • value:函数对象
      • writable: 可写的
      • enumerable: 可枚举的
      • configurable: 可配置的
//普通装饰器
const decorator: MethodDecorator = (target, propertyKey, descriptor) => {
  console.log("target:",target,"propertyKey:",propertyKey,"descriptor:",descriptor);
};

class Service {
  public name: string;
  constructor() {
    this.name = "bang";
  }
  @decorator
  getName() {}
}

输出结果
target: {} 
propertyKey: getName 
descriptor: {
  value: [Function: getName],
  writable: true,
  enumerable: false,
  configurable: true
}

//装饰器工厂
const decorator = (params: any) => {
  return (target: any, propertyKey: any, descriptor: PropertyDescriptor) => {
    console.log(params);
    console.log("target:",target,"propertyKey:",propertyKey,"descriptor:",descriptor);
  };
};

class Service {
  public name: string;
  constructor() {
    this.name = "bang";
  }
  @decorator("ces")
  getName() {}
}

输出结果:
ces
target: {} 
propertyKey: getName 
descriptor: {
  value: [Function: getName],
  writable: true,
  enumerable: false,
  configurable: true
}

参数装饰器

  • 用于方法参数,可以用于添加元数据。
  • 接收三个参数
    • 原型对象
    • 方法名称
    • 参数索引
//普通装饰器
const decorator: ParameterDecorator = (target,propertyKey,parameterIndex) => {
    console.log('target:',target,'propertyKey:',propertyKey,'parameterIndex:',parameterIndex)
}

class test {
  public name: string;
  constructor() {
    this.name = "bang";
  }

  getName(name: string, @decorator age: number) {}
}

输出结果:
target: {} propertyKey: getName parameterIndex: 1
//装饰器工厂
const decorator = (params: string) => {
  return (target: any, propertyKey: any, parameterIndex: number) => {
    console.log(params);
    console.log('target:',target,'propertyKey:',propertyKey,'parameterIndex:',parameterIndex)
  };
};

class test {
  public name: string;
  constructor() {
    this.name = "bang";
  }
  getName(name: string, @decorator("ces") age: number) {}
}

输出结果:
ces
target: {} propertyKey: getName parameterIndex: 1

实现Get装饰器

使用装饰器工厂实现Get装饰器

import axios from "axios";

const Get = (url: string) => {
  return (target: any, key: any, descriptor: PropertyDescriptor) => {
    const fn = descriptor.value;
    axios.get(url).then((res) => {
        fn?.(res, {
          status: 200,
          success: true,
        });
      })
      .catch((e) => {
        fn?.(e, {
          status: 500,
          success: false,
        });
      });
  };
};

class Controls {
  public name: string;
  constructor() {
    this.name = "bang";
  }
  //调用一下掘金热榜的接口
  @Get(
    "https://api.juejin.cn/content_api/v1/content/article_rank?category_id=6809637767543259144&type=hot&aid=2608&uuid=7410977319672890932&spider=0"
  )
  getData(res: any, status: any) {
    console.log("status:", status);
    //处理一下数据方便展示
    console.dir(
      res?.data?.data?.map((a: any) => a.content),
      { depth: null }
    );
  }
}

image.png