前言

类装饰器不常用。
本节主要介绍装饰器的相关语法,以及一些需要注意的细节。

关键字
类装饰器、experimentalDecorators

参考资料

  • experimentalDecorators 配置:链接
  • How to remove experimentalDecorators warning in VSCode:链接

    notes

参数
类装饰器的本质是一个函数,该函数接收一个参数,表示该类本身(构造函数本身)。

语法
使用装饰器的语法:@得到一个函数

变量约束为类
在 TS 中,如何约束一个变量为类:

  • Function
  • new (参数) => object「推荐」

执行时间
装饰器函数的运行时间:在类定义后直接运行

返回值
类装饰器可能的返回值:

  • void:仅运行函数
  • 返回一个新的类:会将新的类替换掉装饰目标

多个装饰器
执行顺序:会按照后加入先调用的顺序进行调用。

experimentalDecorators
Experimental support for decorators is a feature that is subject to change in a future release. Set the ‘experimentalDecorators’ option in your ‘tsconfig’ or ‘jsconfig’ to remove this warning.
对装饰器的实验支持是一项在将来版本中更改的功能。设置 experimentalDecorators 选项以删除此警告。

experimentalDecorators选项设置为 true,可以移除 vscode 中的警告信息。 问题描述:设置的 experimentalDecorators 不生效,如下图所示: image.png 解决办法:打开「设置」,搜索「experimental decorators」(参考资料:链接image.png image.png

codes

在 TS 中,约束一个变量为类的两种写法:

  • Function
  • new (参数) => object
  1. function test(target: Function) {
  2. // ...
  3. new target();
  4. // ...
  5. }
  6. @test
  7. class A { }

虽然写 Function 也是 OK 的,但是如果在函数体 test 中,通过 new 关键字来调用 test 的话,那么会报错。

image.png

  1. function test(target: new () => object) {
  2. // ...
  3. new target();
  4. // ...
  5. }
  6. @test
  7. class A { }
  1. function test(target: new () => object) {
  2. console.log(target); // => [class A]
  3. }
  4. @test
  5. class A { }
  6. const a = new A();
  1. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  2. var c = arguments.length,
  3. r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
  4. d;
  5. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  6. else
  7. for (var i = decorators.length - 1; i >= 0; i--)
  8. if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. function test(target) {
  12. console.log(target);
  13. }
  14. let A = class A {};
  15. A = __decorate([
  16. test
  17. ], A);
  18. const a = new A();

编辑结果分析:
编译结果比较复杂,不过仔细观察,主要由几个部分:

  1. __decorate 函数
  2. test 函数
  3. A 类

__decorate 函数
__decorate 函数接收的参数:(decorators, target, key, desc)
核心关注前面两个:

  • decorators:所有的装饰器
  • target:这些装饰器作用的目标

decorate 函数的调用:`decorate([ test ], A)`

  • [ test ]:它会将所有的装饰器合并到一个数组中,作为它的第一个参数传入,这里我们只使用到一个 test 装饰器,所以数组中只有一个成员 test。
  • A:这些装饰器作用的目标是一个类,将这个类作为第二个参数传入。

__decorate 函数的返回值是一个经过装饰器处理后的新的构造函数。

test 函数
test 函数是一个装饰器

A 类
A 最终的值将会是 __decorate 的返回值。

  1. function test(target: new () => object) {
  2. return class B {};
  3. }
  4. @test
  5. class A {}
  6. const a = new A();
  7. console.log(a); // => B {}

装饰器 test 的返回值,就是 A() 得到的结果。
new A()new B() 是等效的。

  1. function test(target: new () => object) {
  2. return class B extends target {};
  3. }
  4. @test // ×
  5. class A {
  6. prop1: string;
  7. }
  8. const a = new A();
  9. console.log(a.prop1);

image.png

装饰函数返回类型“typeof B”不能赋给类型“void | typeof A”。类型“typeof B”不可赋给类型“typeof A”。类型“{ 0 }”中缺少属性“prop1”,但类型“A”中需要该属性。

return class B extends target {}
虽然 A 中确实含有 prop1 属性,按理来说,B 继承 A,那么 B 自然也有 prop1 属性,所以上述程序,直接看来是没有隐患的。但是 test 装饰器的参数 target 是动态的,它不一定就是 A。

这种写法,也就是在学习的时候会介绍介绍,实际开发中也不会这么写,需要知道这么写会导致的问题就可以了。

  1. function test(target: new () => object) {
  2. }
  3. @test // ×
  4. class A {
  5. prop1: string
  6. constructor (public prop2: string) {
  7. }
  8. }

image.png
“typeof A”类型的参数不能赋给“new () => object”类型的参数。构造签名的类型不兼容。类型“new (prop2: string) => A”不能赋给类型“new () => object”。

test 装饰器的参数 target 约束不满足条件,所以会报错,需要修改 target 的约束条件。

new () => object:不能接收参数
new (...args: any[]) => object:可以接收若干个参数

  1. function test(des: string) {
  2. return function (target: new (...args: any[]) => object) {
  3. // ...
  4. };
  5. }
  6. @test('这是一个类')
  7. class A {
  8. prop1: string;
  9. }

@test('这是一个类') 返回值是一个装饰器

  1. type constructor = new (...args: any[]) => object;
  2. function d1(target: constructor) {
  3. console.log("d1");
  4. }
  5. function d2(target: constructor) {
  6. console.log("d2");
  7. }
  8. @d1
  9. @d2
  10. class A {
  11. prop1: string;
  12. }

若出现多个装饰器,执行顺序会按照后加入先调用的顺序进行调用:
先输出 d2
再输出 d1

  1. type constructor = new (...args: any[]) => object;
  2. function d1() {
  3. console.log("d1");
  4. return function (target: constructor) {
  5. console.log("d1 decorator");
  6. }
  7. }
  8. function d2() {
  9. console.log("d2");
  10. return function (target: constructor) {
  11. console.log("d2 decorator");
  12. }
  13. }
  14. @d1()
  15. @d2()
  16. class A {
  17. prop1: string;
  18. }

打印结果:
d1
d2
d2 decorator
d1 decorator

d1 和 d2 是普通的函数,它们的返回值才是装饰器。
普通的函数,写在前边的先执行。所以会先执行 d1 再执行 d2
构造器函数,写的位置与类的距离越近,越先执行。所以会先执行装饰器 d2() 再执行装饰器 d1()