ts 装饰器

原创
08/23 22:40
阅读数 60

ts 装饰器不支持函数, 只能在类上面加, 有点难受....

https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories

 

 

由于tsdx只是一个库打包工具, 不会开启server所以我们需要使用parcel做一层转发

或者使用storybook

npx tsdx ioc-demo
或者
npx tsdx create ioc-demo

npm install inversify reflect-metadata parcel-bundler --save
 
yarn add inversify reflect-metadata  parcel-bundler 

yarn start
yarn dev

 

tsconfig

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["es6"],
        "types": ["reflect-metadata"],
        "module": "commonjs",
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

 

例子

可以看到确实已经拓展了方法, 加了run函数, 但是ts的类型提示还是有问题, 得好好研究下

import _debounce from 'lodash-es/debounce';
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('log:', target, propertyKey, descriptor);
}

function debounce(wait: number = 100) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    console.log('debounce:', wait, target, propertyKey, descriptor);
    descriptor.value = _debounce(descriptor.value, wait);
  };
}

interface IRun {
  hello:string;
  run():void;
}
function addRun<T extends {new(...args:any[]):{}}>(constructor: T) {
  return class extends constructor implements IRun {
    // newProperty = "new property";
    hello = 'override';
    run() {
      console.log('run', this);
    }
  };
}

@addRun
class Stu {
  name: string = '';
  constructor(name: string = '') {
    this.name = name;
  }

  @log
  play() {
    console.log('play: ', this.name);
  }

  @debounce(100)
  study() {
    console.log('study2: ', this.name);
  }
}

const s = new Stu('ace');

for (let i = 0; i < 3; i++) s.play();
for (let i = 0; i < 3; i++) s.study();
console.log('s', s);
// s.run()

 

数组参数必须传入

先执行参数装饰器, 再执行方法装饰器, 在执行参数装饰器时, 加上一个该方法需要必传参数的索引数组, 然后在方法装饰器中对传入方法的数组做校验


import 'reflect-metadata';

const requiredMetadataKey = Symbol('required');

function required(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  console.log('required');
  let existingRequiredParameters: number[] =
    Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata(
    requiredMetadataKey,
    existingRequiredParameters,
    target,
    propertyKey
  );
}

function validate(target: any, propertyName: string, descriptor: any) {
  console.log('validate');
  let method = descriptor.value;
  descriptor.value = function(...args: any[]) {
    let requiredParameters: number[] = Reflect.getOwnMetadata(
      requiredMetadataKey,
      target,
      propertyName
    );
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if (
          parameterIndex >= args.length ||
          args[parameterIndex] === undefined
        ) {
          console.error('Missing required argument.');
        }
      }
    }

    return method.apply(this, args);
  };
}

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @validate
  greet(@required name: string) {
    return 'Hello ' + name + ', ' + this.greeting;
  }
}

const g = new Greeter('ace');

console.log(g.greet('abc'));
g.greet();

 

 

ts中的接口和类

interface I {
  I(): void;
}
class A {
  A() {
    console.log('A');
  }
}

class B extends A implements I {
  B() {
    console.log('B');
  }
  I() {
    console.log('I');
  }
}

const b = new B();
b.A();
b.B();
b.I();

 

 

修改属性访问器

// 注意  TypeScript不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。

// 访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

// 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
// 成员的名字。
// 成员的属性描述符。
// 注意  如果代码输出目标版本小于ES5,Property Descriptor将会是undefined。

// 如果访问器装饰器返回一个值,它会被用作方法的属性描述符。

// 注意  如果代码输出目标版本小于ES5返回值会被忽略。
function configurable(value: boolean) {
  return function(descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
    descriptor.writable = value;
  };
}

class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  @configurable(false)
  get x() {
    return this._x;
  }

  @configurable(false)
  get y() {
    return this._y;
  }
}

const p = new Point(1, 1);
console.log(p.x);
console.log(p.y);
p.x = 2 // 会报错, 不能设置新值

 

属性格式化

import 'reflect-metadata';

const formatMetadataKey = Symbol('format');

function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
  @format('Hello, %s')
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    let formatString = getFormat(this, 'greeting');
    return formatString.replace('%s', this.greeting);
  }
}

const g = new Greeter('ace');
console.log(g.greet());

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部