TypeScript学习日记-装饰器

286 阅读3分钟

一种特殊类型声明, 可以被附加到类声明, 方法, 属性或参数上,可以修改类的行为

启用装饰器

装饰器是一项实验性特性,如果要启用你必须在命令行或tsconfig.json里启用

命令行
tsc --target ES5 --experimentalDecorators
tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

装饰器工厂

若要要定制一个修饰器如何应用到一个声明上,我们得写一个装饰器工厂函数。

function log(params: any) {
    return function (target: any) {
        console.log(params); // 装饰器所装饰的类-HttpClient
        console.log(target); // 装饰器传入的值-'123'
    }
}
@log('123')
class HttpClient { }

组合装饰器

一个对象可以添加多个装饰器

执行顺序是依次执行

// 书写在同一行上:
@f @g x
// 书写在多行上:
@f
@g
x

类装饰器

在类声明前被声明, 紧靠类

// 普通装饰器
function logClass(params: any) {
    console.log(params); // [Function: HttpClient] 即装饰器注入的类
    params.prototype.apiUrl = "xxx"
    params.prototype.getApi = () => params.prototype.apiUrl
}

@logClass
class HttpClient {
    cosntructor() { }
    getData() { }
}

var http: any = new HttpClient()
console.log(http.apiUrl);  // xxx

// 装饰器工厂
function logClass(params: string) {
    return function (target: any) {
        console.log(params); // HttpClient类
        console.log(target); // hello
        target.prototype.apiUrl = params
    }
}

@logClass('hello') // 工厂装饰器必须传参
class HttpClient {
    cosntructor() { }
    getData() { }
}
var http = new HttpClient()
console.log(http.apiUrl)// hello (但是这里会报错-类型“HttpClient”上不存在属性“apiUrl”)

装饰器也可以重新定义类方法和属性, 即重载类

function logClass(params: any) {
    return class extends params {
        apiUrl = '修改后'
        getData() { // 不重载, 提示缺少属性 "getData",但类型 "HttpClient" 中需要该属性
            console.log('装饰器' + this.apiUrl);
        }
    }
}

@logClass
class HttpClient {
    public apiUrl: string | undefined = '我是构造函数apiUrl'
    getData() {
        console.log('原本' + this.apiUrl);
    }
}
var http = new HttpClient()
http.getData() // 装饰器修改后

属性装饰器

会在运行时当作函数被调用,传入两个参数

  1. 静态成员时类的构造函数, 实例成员时类的原型对象
  2. 成员的名字
function logProperty(params: any) {
    return function (target: any, attr: any) {
        console.log(params) // 123-传入的参数
        console.log(target); // HttpClient { cosntructor: [Function], getData: [Function] }-类的原型对象HttpClient类
        console.log(attr); // apiUrl-apiUrl属性名
        target[attr] = params
    }
}
class HttpClient {
    @logProperty('123')
    public apiUrl: string | undefined
    cosntructor() {
    }
    getData() {
        console.log(this.apiUrl);
    }
}
var http: any = new HttpClient()
console.log(http.apiUrl) // '123'

方法装饰器

放在方法上,用于监视,修改或替换方法的定义

传入三个参数

  1. 静态成员时类的构造函数, 实例成员时类的原型对象
  2. 方法的名字
  3. 方法的属性描述符
function logMethod(params: any) {
    return function (target: any, methodName: any, desc: any) {
        console.log(params) // 123-传入的参数
        console.log(target); // HttpClient { cosntructor: [Function], getData: [Function] }-类的原型对象HttpClient类
        console.log(methodName); // getData-方法名
        console.log(desc); // {value: [Function], writable: true,enumerable: true,configurable: true}-方法属性
        // desc.value 当前方法
        const omethod = desc.value
        desc.value = function (...args: any[]) {
            console.log('这是注释器添加的方法')
            // 修改getData的方法
            omethod.apply(this, args) // 同时附加原方法
        }
    }
}
class HttpClient {
    public apiUrl: string | undefined
    constructor(a: string) {
        this.apiUrl = a
    }
    @logMethod('123')
    getData() {
        console.log(this.apiUrl);
    }
}

const h = new HttpClient('123');
h.getData() // 这是注释器添加的方法 123

参数装饰器

传入三个参数

  1. 静态成员时类的构造函数, 实例成员时类的原型对象
  2. 参数的名字
  3. 参数在函数列表中索引
function logArgument(params: any) {
    return function (target: any, Name: any, index: any) {
        console.log(params) // 123-传入的参数
        console.log(target); // HttpClient { getData: [Function] }-类的原型对象HttpClient类
        console.log(Name); // getData-方法名 (这里又和官网解释不同, 期望有大神解答下)
        console.log(index); // 0-参数在函数列表中索引
    }
}
class HttpClient {
    getData(@logArgument('123') apiUrl: string) {
        console.log(apiUrl);
    }
}

执行顺序

function logArgument(params: any) {
    return function (target: any, Name: any, index: any) {
        console.log('参数装饰器')
    }
}

function logMethod(params: any) {
    return function (target: any, methodName: any, desc: any) {
        console.log('方法装饰器')
    }
}
function logProperty(params: any) {
    return function (target: any, attr: any) {
        console.log('属性装饰器')
    }
}
function logClass(params: string) {
    return function (target: any) {
        console.log('类装饰器')
    }
}

@logClass('Class')
class HttpClient {
    @logProperty('Property')
    apiUrl: string = 'apiUrl'
    @logMethod('Method')
    getData(@logArgument('Argument') apiUrl: string) {
        console.log(apiUrl);
    }
}
// 属性装饰器 => 参数装饰器 => 方法装饰器 => 类装饰器