TypeScript – Decorator 装饰器

前言

TypeScript 5.0 之后就可以使用正真的 JS Decorator 了, 从前 experiment 的版本依然可用, 但是不建议继续用, 因为差很远, 一起用会混乱.

Decorator 的概念 Python 也有. 它就是设计模式里的装饰者模式, 用来扩展已有的功能.

和 Decorator 长得很像的还有 Annotation. 这个 Java 和 C# 都有 (C# 叫 Attribute).

长得像但是功能却不一样哦. Annotation 是做 meta 标签的, 通过反射获取 metadata 然后做一些逻辑处理. 这个和 Decorator 的扩展原有功能, 简直八竿子打不着一边.

TypeScript experiment 的 Decorator 很妙, 因为它有 Decorator 装饰的概念, 同时配上 reflect-metadata 还能实现 Annotation 功能. 但 5.0 后的 Decorator 移除了 Annotation 的部分, 只保留了 Decorate 的特性. Annotation 被独立了出来.

想知道它们的前世今生可以看这篇. 我就不多说了.

 

参考:

Medium – TS 5.0 Beta: New Decorators Are Here!

Docs – Announcing TypeScript 5.0 Beta

JavaScript metaprogramming with the 2022-03 decorators API (in-depth)

YouTube – Decorators! Coming in TypeScript 5

 

esbuild 目前不支持

Github Issus – Feature request: Decorators support

想尝试 Decorator 请使用 tsc,

 

How Decorator Look Like?

Decorator 是一个函数. 

function myDecorator(target: any, context: DecoratorContext): any {
  console.log([target, context.kind]);
}

调用的方式是在 class 的几个部位前, 加上 @decoratorFunction

@myDecorator
class Person {
  @myDecorator
  #name = '';

  @myDecorator
  get name(): string {
    return this.#name;
  }

  @myDecorator
  set name(name: string) {
    this.#name = name;
  }

  @myDecorator
  accessor age = 11;

  @myDecorator
  method(): void {
    console.log('do something...');
  }
}

myDecorator 虽然是函数, 但使用的时候 @myDecorator 是不需要括弧的.

而 @createMyDecorator('some options') 则是调用一个 decorator 工厂函数, 制作出 decorator 函数. 

class 许多部位都可以使用 Decorator, 比如 class, field, method, getter, setter, accessor (accessor 是非常新的东西, 专门给 decorator 用的, 太新了这篇不会介绍)

 

How Decorator Work? (e.g. ClassMethodDecorator)

来看一个扩展方法的 Decorator

首先是一个简单的 class 有一个方法

class Person {
  sayHi(): void {
    console.log('do something...');
  }
}
const person = new Person();
person.sayHi();

我们想扩展这个方法. 在方法执行前后加上一些逻辑.

首先, Decorator Function 长这样

type Method = (this: unknown, ...arg: unknown[]) => unknown;
function methodDecorator(target: Method, context: ClassMemberDecoratorContext): Method {
  // target 就是当前被装饰的 class 方法
  const originalMethod = target;

  // 定义一个新方法
  const decoratedMethod: Method = function (this, ...args) {
    console.log('before method call'); // 扩展方法
    const returnValue = originalMethod.call(this, ...args); // 调用原有方法
    console.log('after method call'); // 扩展方法
    return returnValue;
  };

  // 返回装饰后的方法
  return decoratedMethod;
}

调用 Decorator

class Person {
  @methodDecorator
  sayHi(): void {
    console.log('do something...');
  }
}

当 JS 运行到 class 这里时 (注: 不是实例化的时候哦, 是 definition 的时候), methodDecorator 会被调用

sayHi 方法会被传入, 同时还有其上下文 context, 比如, 方法名, 类型 (是 class, field, method 其它...) 

然后 methodDecorator 内部创建出 decoratedMethod 替换掉原来的方法.

以上就是基本的 decorator 使用方式. 你可以把它看成是 metaprogramming 的一种.

 

ClassDecorator

接下来, 一个一个例子看. 概念都差不多

type Class = {
  new (...args: any[]): Object;
};

function myClassDecorator<TClass extends Class>(
  target: TClass,
  context: ClassDecoratorContext
): TClass {
  return target;
}

@myClassDecorator
class Person {}

class decorator 可以装饰一个 class. 

它的玩法和上面的 method decorator 一样,  输入一个 class, 返回一个 class

我们可以对这个 class 做许多事情

function myClassDecorator<TClass extends Class>(
  target: TClass,
  context: ClassDecoratorContext
): TClass {
// 增加一个方法给 class target.prototype.method = function () { console.log('add on method'); }; // 增加一个 static 属性 target['addonStaticProperty'] = 'addon static property'; // 派生类 const derivedClass = class extends target { childValue = 'child value'; }; // 返回派生类 return derivedClass; }

调用

@myClassDecorator
class Person {}

console.log(Person['addonStaticProperty']); // 'addon static property'
console.log(Person.name); // Ali
const ali = new Person();
console.log(ali instanceof Person); // true
ali['method'](); // add on method
console.log(ali['childValue']); // child value

addInitializer

@myClassDecorator
class Person {
  static value = 'default value';
}

在 static value 还没有 init 前, addInitializer 会触发

// emit before decorate class
// 这个 class static value 还没有 init, 都是 undefined
context.addInitializer(() => {
  console.log(target['staticValue']); // undefined
});

至于要在这个阶段干什么, 我目前没有想到...

 

ClassMemberDecorator

ClassMemberDecorator 是 ClassMethod, ClassField, ClassGetter, ClassSetter, ClassAccessor 的抽象. (注: ClassMethod 上面介绍过了, 这里不再介绍)

ClassMemberDecoratorContext 同样是抽象. 它的 kind 有 method, field, getter, setter, accessor

这个是最简单的 MemberDecorator

function memberDecorator(target: any, context: ClassMemberDecoratorContext): void {}

target 有可能是 method or undefined, depend on kind.

memberDecorator 可以有 return, 也可以没有. return type 也 depend on kind.

Standard Member Info

1. target & kind

for field

class Person {
  @memberDecorator
  name = 'default value';
}

function memberDecorator(target: any, context: ClassMemberDecoratorContext) {
  console.log(target === undefined); // true
  console.log(context.kind); // field
}

for getter

@memberDecorator
get age() {
  return 10;
}

console.log(target); // the get method
console.log(context.kind); // getter

2. name

@memberDecorator
age = 11;

console.log(context.name); // age

3. is static?

@memberDecorator
static age = 11;

console.log(context.static); // true

4. is private?

@memberDecorator
get #age() {
  return 11;
}

console.log(context.name); // #age
console.log(context.private); // true

addInitializer

和 ClassDecorator 不同, MemberDecorator 的 addInitializer 会在每一次 class 被实例化的时候触发 (before constructor)

class Person {
  constructor() {
    console.log('constructor');
  }

  @memberDecorator
  name = 'name';
}

const p1 = new Person();
const p2 = new Person();

function memberDecorator(target: any, context: ClassMemberDecoratorContext) {
  context.addInitializer(() => {
    console.log('call');
  });
}

效果

 

ClassFieldDecorator

ClassMemberDecorator 是抽象, ClassFieldDecorator 是它的具体. 

ClassFieldDecorator  不能用于 method, get, set 和 accessor.

.name, .kind, .private, .static 这些都和 member 一样, 这里不再复述.

好, 我们从例子里学,

readonly decorator

class Person {
  @readonly
  name = 'default name';
}
const p1 = new Person();
p1.name = 'new name'; // should popup error

添加 readonly decorator 到某个属性上, 这个属性就会变成 readonly.

FiledDecorator 是透过 return function 来操控 property config 的.

function readonly(target: undefined, context: ClassFieldDecoratorContext) {

  // return a function for decorate property
  return function (initValue: any) {

    console.log(this instanceof Person); // true
    
    return initValue; // default value
  };

}

这个 return function 会在 Person 实例化时被调用 (before constructor)

return function 的 this 是 class instance, 参数是这个 property 的 init value.

function 可以返回一个加工后的 init value. 那在 constructor 阶段就会拿到新的 init value 了.

通过 class instance + property name. 我们可以做各种 property config.

function readonly(target: undefined, context: ClassFieldDecoratorContext) {
  return function (initValue: any) {

    Object.defineProperty(this, context.name, {
      writable: false,
    });
    
    return initValue;
  };
}

Object.defineProperty { writable: false } 后, 这个 property 就变成 readonly 了.

执行顺序

 

 

ClassGetterDecorator

和 ClassMehtodDecorator 大同小异.

class Person {
  firstName = 'name';
  lastName = 'lastName';

  @getterDecorator
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}
const person = new Person();
console.log(person.fullName);

function getterDecorator(target: () => any, context: ClassGetterDecoratorContext) {
  const originalGetter = target;
  return function () {
    console.log(this instanceof Person); // true
    return originalGetter.call(this) + ' decorated';
  };
}

target 是原本的 getter, 返回的函数 this 指向 class instance.

 

posted @ 2023-02-15 15:56  兴杰  阅读(555)  评论(1编辑  收藏  举报