初识TS装饰器

作者: 没名字的某某人 | 来源:发表于2022-06-05 19:45 被阅读0次

写在最前:本文转自掘金

前言

我们平常开发中或多或少的听说使用过装饰器,也切身感受到它带给我们的遍历。本文将聚焦ts的装饰器,去探讨什么是装饰器,如何使用。

装饰器的演变

  • 2015-3-24
    stage1 阶段,也是目前广为使用的用法,也基本等同ts开启了experimentalDecorators的用法。
  • 2018-09
    进入到stage2阶段,用法和stage1很大不同
  • 2012-12
    针对stage2天进行了一次修改。
  • 2022-03
    正是进入stage3,去掉了matedata部分,使用方式没有太大变化。

js装饰器和ts装饰器

js原生目前不支持装饰器,只能通过babel体验装饰器这个新特性。

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。装饰器使用@expression这种形式,expression求职后必须为一个函数,它会再运行时被调用,被装饰的声明信息作为参数传入。

由于装饰器目前还是实验中的特定,在js中处于stage-3阶段。在ts中已经作为一项实验性予以支持。开启装饰器需要在tsconfig.json文件中启用 experimentalDecorators 编译器选项。

装饰器的使用

类装饰器

类装饰器是我们最常使用到的,它的通常作用是,为该类扩展功能

  • 类装饰器有且只有一个,参数为类的构造函数constructor
  • 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明

设想有这样一个场景。目前有一个Tank类,有一个Plane类,有一个Animal类。这三个类都需要一个公共的方法来获取他们所在的位置。我们第一可能想到使用继承来实现。

class BaseClass {
    getPosition() {
        return {
            x: 100,
            y: 200,
            z: 300,
        }
    }
}
class Tank extends BaseClass{}
class Plane extends BaseClass {}
class Animal extends BaseClass {}

这样三个类都可以调用getPosition方法来获取各自的位置了。到目前为止看起来没有什么问题。
现在又有新需求,Tank类和Plane类需要一个新的方法addPetrol来给坦克和飞机加油。而动物不需要加油。此时这种写法好像不能继续进行下去了。而js目前没有直接语法提供多继承的功能,我们的继承方向好像行不通了。这个时候类装饰器可以很完美的实现这样的功能。

装饰器功能之一——能力扩展
我们把getPositionaddPertrol都抽象成一个单独的功能,它们得作用是给宿主扩展对应的功能。

const getPositionDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.getPosition = () => {
        return [100, 200]
    }
}

const addPetrolDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.addPetrol = () => {
        // do something
        console.log(`${constructor.name}进行加油`);
    }
}

@addPetrolDecorator
@getPositionDecorator
class Tank {}
@addPetrolDecorator
@getPositionDecorator
class Plane {}

@getPositionDecorator
class Animal {}

这样的话,假如日后我们有其他的需求,都可以对他进行能力扩展,让其具有加油的能力。
注意,多个装饰器叠加的时候,执行顺序为离被装饰对象越近的装饰器越先执行。

装饰器功能之二——重载构造函数
在类装饰器中如果返回一个值,它会使用提供的构造函数来替换类的声明。

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

方法装饰器

方法装饰器接收三个参数:

  • 对于静态方法,第一个参数为类的构造函数。对于实例方法,为类的原型对象
  • 第二个参数为方法名。
  • 第三个参数为方法描述符。
  • 方法装饰器可以有返回值,返回值会作为方法的属性描述符

装饰器功能之一——能力增强
我们代码编写时,经常会做一些错误catch,使用装饰器对每个方法进行增加,使它们自动获取catch错误的能力~

const ErrorDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
    const sourceMethod = descriptor.value;
    descriptor.value = async function (...args: any) {
        try {
            await sourceMethod.apply(this, args);
        } catch (error) {
            console.error('捕获到了错误');
            // do something
        }
    }
}
class MusicSystem {
    getMusicById(name: string): Promise<{name: string, singer: string}> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (Math.round(Math.random())) {
                    resolve({name: '凤凰传奇', singer: '玲花|曾毅'});
                } else {
                    reject()
                }
            }, 1000);
        })
    }
    
    @ErrorDecorator
    async play(name: string) {
        const music = await this.getMusicById(name);
        // ... do something
        console.log(`在曲库中找到了名为${music.name}的音乐,由${music.singer}进行演唱,敬请欣赏。`);

    }

    @ErrorDecorator
    async deleteByName(name: string) {
        const music = await this.getMusicById(name);
        // ... do something
        console.log(`${music.name}音乐删除成功!`);
    }
}

const musicSystem = new MusicSystem();
musicSystem.play('凤凰传奇');
musicSystem.deleteByName('凤凰传奇');

细心的同学可以发现了,我们在方法装饰器中无法捕获到实际的错误,比如精准报错哪首歌没找到。很遗憾,目前装饰器的原生能力,是无法获取到我们调用时候传入的具体参数的。因为装饰器实在编译阶段执行的。但是,我们可以通过其他方式实现这样的功能,这就是大名鼎鼎的 metadata 。我们会在文章的末尾提到它。

装饰器功能之一——descriptor修改
通过修改descriptor,我们可以实现对方法进行重新描述。比如设置方法禁止修改,禁止删除等。

const DescriptorDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) : object => {
    return {
        value: () => {
            console.log('eat方法被替换')
        },
        writable: true,
        enumerable: true,
        configurable: true,
    };
}

class Pig {
    name = 'peiqi';
    @DescriptorDecorator
    eat() {

    }
}

同样的,也可以直接对descriptor进行修改

descriptor.value = () => {console.log('eat方法被替换')};
descriptor.writable = true;
descriptor.enumerable = true;
descriptor.configurable = true;

方法装饰器的使用方式很多,大多数的使用方式是对descriptor的value属性进行替换,拦截等实现功能。

【下边的三个装饰器类型,相对来说使用比较少,有兴趣的小伙伴可以查看原文】

属性装饰器

参数装饰器

访问器装饰器

相关文章

  • 初识TS装饰器

    写在最前:本文转自掘金[https://juejin.cn/post/7095717238149218317] 前...

  • 装饰器实验

    装饰器实验 说明 ts内包含了四个装饰器,类装饰器、属性装饰器、函数装饰器、参数装饰器,本文中测试一下其的使用。 ...

  • TS装饰器

    装饰器是一种特殊类型的声明,本质上就是一个方法,可以注入到类、方法、属性、参数上,扩展其功能; 常见的装饰器:类装...

  • TS装饰器

    装饰器是一种特殊类型的声明,可以被附加到类生命、方法、访问符、属性或参数上,可以修改类的行为。使用@express...

  • ts装饰器

    1、装饰器是什么?装饰器是一种特殊类型的声明,他能够附加到类声明、属性、方法、参数上,可以修改类的行为。通俗的讲,...

  • TS装饰器

    一:类的装饰器:是一种与类(class)相关的语法,用来注释或修改类和类方法,装饰器本身是一个函数,装饰器通过@来...

  • 从零开发ts装饰器管理koa路由

    前言 两年前刚学ts,当时搭了个简单的koa的demo,介绍了如何用装饰器管理koa的路由:TS装饰器初体验,用装...

  • TS 装饰器(1): 基础用法

    TS 装饰器(1): 基础用法 1、什么是装饰器 装饰器是通过添加标注的方式,来对类型进行扩展的一种方式。 只能在...

  • ts装饰器写法

    今天做项目的时候发现要用到watch来监听,所以就学习了watch的装饰器写法,然后顺便把之前用过的都看了,这里做...

  • 装饰器-箭头函数之探索

    装饰器介绍 初识装饰器,还是在 babel-preset-stage-0 提案法则中被支持,学过 Java 的想必...

网友评论

    本文标题:初识TS装饰器

    本文链接:https://www.haomeiwen.com/subject/gxukmrtx.html