前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在TypeScript中使用装饰器

在TypeScript中使用装饰器

作者头像
luciozhang
发布2023-04-22 16:43:39
9930
发布2023-04-22 16:43:39
举报
文章被收录于专栏:前端lucio前端lucio

Decorator装饰器是ES7的时候提案的特性,目前处于Stage 3候选阶段(2022年10月)。

装饰器简单来说就是修改类和类方法的语法糖,很多面向对象语言都有装饰器这一特性。

接上文,在JS中使用装饰器,本文介绍一下在TS中使用装饰器。

在TypeScript中使用装饰器

TypeScript已经将装饰器作为一项实验性特性支持了,我们可以直接通过修改配置开启装饰器特性。

开启特性

tsconfig.json:

代码语言:javascript
复制
{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

使用

一图了解TypeScript的语法。

ts装饰器语法
ts装饰器语法

语法: @+函数名

代码语言:javascript
复制
@frozen
class Foo {
  @throttle(500)
  expensiveMethod() {}
}

类装饰器

类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。

类型声明
代码语言:javascript
复制
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

返回值用于代替构造器。因此适合用于继承一个现有类并添加一些属性和方法。

示例
代码语言:javascript
复制
type Consturctor = { new (...args: any[]): any };

function toString<T extends Consturctor>(BaseClass: T) {
  return class extends BaseClass {
    toString() {
      return JSON.stringify(this);
    }
  };
}

@toString
class C {
  public foo = "foo";
  public num = 24;
}

console.log(new C().toString())
// -> {"foo":"foo","num":24}

属性装饰器

可以用来监视、修改或替换一个属性的定义。

类型声明
代码语言:javascript
复制
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

返回值被忽略。

示例
代码语言:javascript
复制
function logParameter(target: Object, propertyKey: string) {
    let value = target[propertyKey]

    Object.defineProperty(target, propertyKey, {
      set: function (newVal) {
        value = newVal
        console.log(`Set: ${propertyKey} => ${value}`);
      },
      get: function () {
        console.log(`Get: ${propertyKey} => ${value}`);
        return value
      },
      enumerable: true,
      configurable: true
    })
}

class Employee {
@logParameter
    name: String;
}

const emp = new Employee();
emp.name = "Mohan Ram"; // Set: name => Mohan Ram
emp.name; // Get: name => Mohan Ram

方法装饰器

可以用来监视、修改或者替换方法定义。

类型声明
代码语言:javascript
复制
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

返回值用于替换属性装饰器。

示例
代码语言:javascript
复制
function writable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(target)
        console.log(propertyKey)
        console.log(descriptor)

        descriptor.writable = value
    }
}

class Welcome {
  user: string
  constructor(user: string) {
    this.user = user
  }

  @writable(false)
  showWarn() {
    return "Hello, " + this.user
  }
}

const welcome = new Welcome('Peter')

welcome.showWarn = () => {  // 报错不能修改只读的类成员
  return '1'
}

访问器装饰器

可以用来监视、修改或替换一个访问器的定义。

注意  TypeScript 不允许同时装饰一个成员的 get 和 set 访问器。因此,如果想为一个成员的访问器添加装饰器,则必须添加在该成员在文档顺序上的第一个访问器前。因为装饰器应用于属性描述符时联合了 get 和 set 访问器,而不是分开声明的。

类型声明

同方法装饰器。

返回值用于替换属性装饰器。

但属性装饰器的key不同: 方法装饰器的描述器的key为:

  • value
  • writable
  • enumerable
  • configurable

访问器装饰器的描述器的key为:

  • get
  • set
  • enumerable
  • configurable
示例
代码语言:javascript
复制
function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      descriptor.configurable = value
      console.log(descriptor)
    }
}

class Welcome {
    user: String
    constructor(user: String) {
        this.user = user
    }

    @configurable(false)
    set setUser(user: String) {
        this.user = user
    }

    get getUser() {
        return 'Hello, ' + this.user
    }
}

const welcome = new Welcome('Peter')
console.log(welcome.getUser)
// {
//     get: undefined,
//     set: [Function: set setUser],
//     enumerable: false,
//     configurable: false
// }
// Hello, Peter

参数装饰器

单独的参数装饰器能做的事情很有限,它一般都被用于记录可被其它装饰器使用的信息。

类型声明
代码语言:javascript
复制
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

返回值被忽略。

示例
代码语言:javascript
复制
function required(target: any, key: string, index: number) {
  console.log(target === A)
  console.log(target === A.prototype)
  console.log(key)
  console.log(index)
}
class A {
  saveData(@required name: string){}  // 输出 false true name 0
}

装饰器工厂

当我们需要给装饰器传自定义参数时,需要构造一个装饰器工厂函数。

代码语言:javascript
复制
type Consturctor = { new (...args: any[]): any };

function testable(isTestable:boolean) {
    return <T extends Consturctor>(BaseClass: T) => {
        return class extends BaseClass {
            isTestable:boolean = isTestable;
        }
    }
}

@testable(true)
class MyTestableClass {
    isTestable:boolean
};
console.log((new MyTestableClass()).isTestable); // true

@testable(false)
class MyClass {
    isTestable:boolean
};
console.log((new MyClass()).isTestable); // false

执行顺序

多个装饰器同时应用于一个对象时,装饰器工厂函数从上至下开始执行,装饰器函数从下至上开始执行。

代码语言:javascript
复制
function first() {
  console.log("first(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called");
  };
}

function second() {
  console.log("second(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");
  };
}

class ExampleClass {
  @first()
  @second()
  method() {}
}
// first(): factory evaluated
// second(): factory evaluated
// second(): called
// first(): called

应用

为vue的添加@props装饰器。

我们先来看下,正常情况下定义一个props的代码。

代码语言:javascript
复制
<template>
  <div style="display: flex;">
    {{msg}}
  </div>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    msg: {
      type: String,
      default: 'Hi',
    },
  },
});
</script>

未来简化声明props的代码,我们希望只通过@Prop({type: String, default: 'Hi'})就完成props的声明。

这里我们直接用到了vue-property-decorator这个库。

下面是使用装饰器的写法。

代码语言:javascript
复制
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop({ type: String, default: 'default value' }) readonly msg!: string
}  

我们看一下@Prop装饰器的语法。

简单过程就是获取装饰器参数里面的属性,赋值给vue类的props选项。

这里为了把vue组件转换成类语法,用到了vue-class-component这个库。

代码语言:javascript
复制
import Vue, { PropOptions } from 'vue'
import { createDecorator } from 'vue-class-component'
import { Constructor } from 'vue/types/options'
import { applyMetadata } from '../helpers/metadata'

/**
 * 封装的处理props属性的装饰器
 * @param  options @Prop(options)装饰器内的options参数选项
 * @return PropertyDecorator | void
 */
export function Prop(options: PropOptions | Constructor[] | Constructor = {}) {
  return (target: Vue, key: string) => {
    // 如果@Prop(options)的options不存在type属性,获取被装饰对象的元数据类型属性,赋值给options.type
    applyMetadata(options, target, key)

    // vue-class-component生成的options.props[key]赋值为@Prop的参数options。
    createDecorator((componentOptions, k) => {
      /**
       *  - componentOptions 是vue-class-component生成的options参数
       *  - k 是@Prop装饰器装饰的属性
       *  - options 是@Prop(options)装饰器的options参数
       */
      ;(componentOptions.props || ((componentOptions.props = {}) as any))[
        k
      ] = options
    })(target, key)
  }
}

参考资料

TypeScript中文教程-装饰器

JDR Design-浅析 TypeScript 装饰器

JDR Design-Typescript 装饰器及应用场景浅析

TypeScript装饰器完全指南

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 在TypeScript中使用装饰器
    • 开启特性
      • 使用
        • 语法: @+函数名
        • 类装饰器
        • 属性装饰器
        • 方法装饰器
        • 访问器装饰器
        • 参数装饰器
        • 装饰器工厂
        • 执行顺序
      • 应用
      • 参考资料
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档