TypeScript 装饰器(Decorator)
1. 解释
装饰器的写法:普通装饰器(无法传参)、 装饰器工厂(可传参)。
装饰器是一项实验性特性,在未来的版本中可能会发生改变。
若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json
里启用 experimentalDecorators
编译器选项:
命令行:
tsc --target ES5 --experimentalDecorators
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
2. 装饰器的使用方法
装饰器允许你在类和方法定义的时候去注释或者修改它。装饰器是一个作用于函数的表达式,它接收三个参数 target
、 name
和 descriptor
,然后可选性的返回被装饰之后的 descriptor
对象。
2.1 装饰器工厂
通过装饰器工厂方法,可以额外传参,普通装饰器无法传参。
function log(param: string) {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
console.log('target:', target)
console.log('name:', name)
console.log('descriptor:', descriptor)
console.log('param:', param)
}
}
class Employee {
@log('with param')
routine() {
console.log('Daily routine')
}
}
const e = new Employee()
e.routine()
代码解释:
来看代码的打印结果:
target: Employee { routine: [Function] }
name: routine
descriptor: {
value: [Function],
writable: true,
enumerable: true,
configurable: true
}
param: with param
Daily routine
2.2 装饰器组合
多个装饰器可以同时应用到一个声明上,就像下面的示例:
- 书写在同一行上:
@f @g x
- 书写在多行上:
@f
@g
x
在 TypeScript 里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:
通过下面的例子来观察它们求值的顺序:
function f() {
console.log('f(): evaluated');
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('f(): called');
}
}
function g() {
console.log('g(): evaluated');
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('g(): called');
}
}
class C {
@f()
@g()
method() {}
}
在控制台里会打印出如下结果:
f(): evaluated
g(): evaluated
g(): called
f(): called
3. 类装饰器
function extension<T extends { new(...args:any[]): {} }>(constructor: T) {
// 重载构造函数
return class extends constructor {
// 扩展属性
public coreHour = '10:00-15:00'
// 函数重载
meeting() {
console.log('重载:Daily meeting!')
}
}
}
@extension
class Employee {
public name!: string
public department!: string
constructor(name: string, department: string) {
this.name = name
this.department = department
}
meeting() {
console.log('Every Monday!')
}
}
let e = new Employee('Tom', 'IT')
console.log(e) // Employee { name: 'Tom', department: 'IT', coreHour: '10:00-15:00' }
e.meeting() // 重载:Daily meeting!
函数表达式的写法:
const extension = (constructor: Function) => {
constructor.prototype.coreHour = '10:00-15:00'
constructor.prototype.meeting = () => {
console.log('重载:Daily meeting!');
}
}
@extension
class Employee {
public name!: string
public department!: string
constructor(name: string, department: string) {
this.name = name
this.department = department
}
meeting() {
console.log('Every Monday!')
}
}
let e: any = new Employee('Tom', 'IT')
console.log(e.coreHour) // 10:00-15:00
e.meeting() // 重载:Daily meeting!
代码解释:
4. 作用于类属性的装饰器
如果你熟悉 Object.defineProperty
,你会立刻发现这正是 Object.defineProperty 的三个参数。
function readonly(value: boolean) {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
descriptor.writable = value
}
}
class Employee {
@readonly(false)
salary() {
console.log('这是个秘密')
}
}
const e = new Employee()
e.salary = () => { // Error,不可写
console.log('change')
}
e.salary()
解释: 因为 readonly
装饰器将数据描述符中的 writable
改为不可写,所以倒数第三行报错。
5. 方法参数装饰器
注意第三个参数的不同。
function log(param: string) {
console.log(param)
return function (target: any, name: string, index: number) {
console.log(index)
}
}
class Employee {
salary(@log('IT') department: string, @log('John') name: string) {
console.log('这是个秘密')
}
}
6. 装饰器执行顺序
function extension(params: string) {
return function (target: any) {
console.log('类装饰器')
}
}
function method(params: string) {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
console.log('方法装饰器')
}
}
function attribute(params: string) {
return function (target: any, name: string) {
console.log('属性装饰器')
}
}
function argument(params: string) {
return function (target: any, name: string, index: number) {
console.log('参数装饰器', index)
}
}
@extension('类装饰器')
class Employee{
@attribute('属性装饰器')
public name!: string
@method('方法装饰器')
salary(@argument('参数装饰器') name: string, @argument('参数装饰器') department: string) {}
}
查看运行结果: