TypeScript基础入门之装饰器(三)

Easter79
• 阅读 650

转载 TypeScript基础入门之装饰器(三)

继续上篇文章[TypeScript基础入门之装饰器(二)]

访问器装饰器

Accessor Decorator在访问器声明之前声明。 访问器装饰器应用于访问器的属性描述符,可用于观察,修改或替换访问者的定义。 访问器装饰器不能在声明文件中使用,也不能在任何其他环境上下文中使用(例如在声明类中)。

注意: TypeScript不允许为单个成员装饰get和set访问器。相反,该成员的所有装饰器必须应用于按文档顺序指定的第一个访问器。这是因为装饰器适用于属性描述符,它结合了get和set访问器,而不是单独的每个声明。

访问器装饰器的表达式将在运行时作为函数调用,具有以下三个参数:

  1. 静态成员的类的构造函数,或实例成员的类的原型。
  2. 成员的名字。
  3. 会员的财产描述。

注意: 如果脚本目标小于ES5,则属性描述符将不确定。

如果访问器装饰器返回一个值,它将用作该成员的属性描述符。

注意: 如果脚本目标小于ES5,则忽略返回值。

以下是应用于Point类成员的访问器装饰器(@configurable)的示例:

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; }
}

我们可以使用以下函数声明定义@configurable装饰器:

function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}

属性装饰器

Property Decorator在属性声明之前声明。 属性修饰器不能在声明文件中使用,也不能在任何其他环境上下文中使用(例如在声明类中)。

属性装饰器的表达式将在运行时作为函数调用,具有以下两个参数:

  1. 静态成员的类的构造函数,或实例成员的类的原型。
  2. 成员的名字。

注意: 由于在TypeScript中如何初始化属性装饰器,因此不提供属性描述符作为属性装饰器的参数。这是因为在定义原型的成员时,当前没有机制来描述实例属性,也无法观察或修改属性的初始化程序。返回值也会被忽略。因此,属性装饰器只能用于观察已为类声明特定名称的属性。

我们可以使用此信息来记录有关属性的元数据,如以下示例所示:

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);
    }
}

然后我们可以使用以下函数声明定义@format装饰器和getFormat函数:

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);
}

这里的@format("Hello,%s")装饰器是一个装饰工厂。 当调用@format("Hello,%s")时,它会使用reflect-metadata库中的Reflect.metadata函数为该属性添加元数据条目。 调用getFormat时,它会读取格式的元数据值。

注意此示例需要reflect-metadata库。 有关reflect-metadata库的更多信息,请参阅元数据。

参数装饰器

参数装饰器在参数声明之前声明。 参数装饰器应用于类构造函数或方法声明的函数。 参数装饰器不能用于声明文件,重载或任何其他环境上下文(例如声明类中)。

参数装饰器的表达式将在运行时作为函数调用,具有以下三个参数:

  1. 静态成员的类的构造函数,或实例成员的类的原型。
  2. 成员的名字。
  3. 函数参数列表中参数的序数索引。

注意: 参数装饰器只能用于观察已在方法上声明参数。

将忽略参数装饰器的返回值。

以下是应用于Greeter类成员参数的参数装饰器(@required)的示例:

class Greeter {
    greeting: string;

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

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

然后我们可以使用以下函数声明定义@required和@validate装饰器:

import "reflect-metadata";

const requiredMetadataKey = Symbol("required");

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

function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
    let method = descriptor.value;
    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
                    throw new Error("Missing required argument.");
                }
            }
        }

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

@required装饰器添加一个元数据条目,根据需要标记参数。 然后,@validate装饰器将现有的greet方法包装在一个函数中,该函数在调用原始方法之前验证参数。

注意: 此示例需要reflect-metadata库。有关reflect-metadata库的更多信息,请参阅元数据。

元数据

一些示例使用reflect-metadata库,它为实验元数据API添加了polyfill。 该库尚未成为ECMAScript(JavaScript)标准的一部分。 但是,一旦装饰器被正式采用为ECMAScript标准的一部分,这些扩展将被提议采用。

您可以通过npm安装此库:

npm i reflect-metadata --save

TypeScript包含实验支持,用于为具有装饰器的声明发出某些类型的元数据。 要启用此实验性支持,必须在命令行或tsconfig.json中设置emitDecoratorMetadata编译器选

命令行:

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

tsconfig.json:

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

启用后,只要导入了reflect-metadata库,就会在运行时公开其他设计时类型信息。

我们可以在以下示例中看到这一点:

import "reflect-metadata";

class Point {
    x: number;
    y: number;
}

class Line {
    private _p0: Point;
    private _p1: Point;

    @validate
    set p0(value: Point) { this._p0 = value; }
    get p0() { return this._p0; }

    @validate
    set p1(value: Point) { this._p1 = value; }
    get p1() { return this._p1; }
}

function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
    let set = descriptor.set;
    descriptor.set = function (value: T) {
        let type = Reflect.getMetadata("design:type", target, propertyKey);
        if (!(value instanceof type)) {
            throw new TypeError("Invalid type.");
        }
        set(value);
    }
}

TypeScript编译器将使用@ Reflect.metadata装饰器注入设计时类型信息。 你可以认为它相当于以下TypeScript:

class Line {
    private _p0: Point;
    private _p1: Point;

    @validate
    @Reflect.metadata("design:type", Point)
    set p0(value: Point) { this._p0 = value; }
    get p0() { return this._p0; }

    @validate
    @Reflect.metadata("design:type", Point)
    set p1(value: Point) { this._p1 = value; }
    get p1() { return this._p1; }
}

注意: 装饰器元数据是一个实验性功能,可能会在将来的版本中引入重大更改。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
1. 容器化部署一套云服务 第一讲 Jenkins(Docker + Jenkins + Yii2 + 云服务器))
容器化部署一套云服务系列1\.容器化部署一套云服务之Jenkins(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.cnblogs.com%2Fjackson0714%2Fp%2Fdeploy1.html)一、购买服务器服务器!caeef00
Easter79 Easter79
3年前
TypeScript基础入门
转载TypeScript基础入门泛型泛型类(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.gowhich.com%2Fblog%2F903)
Stella981 Stella981
3年前
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法参考文章:(1)Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.codeprj.com%2Fblo
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k