TypeScript装饰器
TypeScript装饰器
简介
- 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
- 装饰器自2025年在ECMAScript-6中被提出,已将近10年。
- 截止目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持。
- 装饰器有5种:
- 类装饰器
- 属性装饰器
- 方法装饰器
- 访问器装饰器
- 参数装饰器
备注:虽然
TypeScript5.0
中可以直接使用类装饰器
,但为了确保其他装饰器可用,现阶段使用时,仍建议使用experimentalDecorators
配置开启装饰器支持,而且不排除下一版本中,官方会进一步调整装饰器的相关语法。
类装饰器
基本语法
类装饰器是一个应用在
类声明
上的函数
,可以为类添加额外的功能,或添加额外的逻辑。
首先在tsconfig.json
中启用实验性装饰器特性
function Demo(target: Function) {
console.log(target)
}
@Demo
class Person { }
应用举例
定义一个装饰器,实现
Animal
实例调用toString
时返回JSON.stringify
的执行结果。
function ToString(targe: Function) {
targe.prototype.toString = function () {
return JSON.stringify(this)
}
// 禁止添加新属性,现有属性可修改,但是不能删除
Object.seal(targe)
}
@ToString
class Animal {
constructor(
public age: number,
public name: string,
) { }
}
const cat = new Animal(1, "cat")
console.log(cat.toString()) // {"age":1,"name":"cat"}
关于返回值
类装饰器
有
返回值:若类装饰器返回一个新的类,那么这个新类将替换
被装饰的类。
类装饰器无
返回值:若类装饰器无返回值或者undefined
,那被装饰的类不会
被替换。
function ReturnClass(target: Function) {
return class {
test() {
console.log(100)
console.log(200)
console.log(300)
}
}
}
@ReturnClass
class Person {
test() {
console.log(400)
}
}
console.log(Person)
// class {
// test() {
// console.log(100);
// console.log(200);
// console.log(300);
// }
// }
关于构造类型
在TypeScript中,
Function
类型所表示的范围十分广泛,包括:普通函数、箭头函数、方法等等。
但并非Function
类型的函数都可以被new
关键字实例化,例如箭头函数是不能被实例化的,那么TypeScript中该如何声明一个构造函数呢?有以下两种方式:
- 仅声明构造类型
type Constructor = new (...arg:any[]) =>{}
new
:该类型是可以用new
操作符调用。
...args
:构造器可以接受【任意数量】的参数。
any[]
:构造器可以接受【任意类型】的参数。
{}
:返回值是对象(非null、非undefined的对象)。
- 声明构造类型 + 指定静态属性
// 定义一个构造类型,且包含一个静态属性 wife
type Constructor = {
// 构造签名
new(...args: any[]): {}
// wife 属性
wife: string,
};
function test(fn: Constructor) {
}
class Person {
static wife = "alex"
}
test(Person)
替换被装饰的类
对于高级一些的装饰类,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态。
需求:设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取创建时间。
type Constructor = new (...args: any[]) => {}
interface Person {
getTime(): void
}
function LogTime<T extends Constructor>(target: T) {
return class extends target {
createdTime: Date
constructor(...args: any) {
super(...args)
this.createdTime = new Date()
}
getTime() {
console.log(this.createdTime)
}
}
}
@LogTime
class Person {
constructor(
public name: string,
public age: number,
) { }
}
const p1 = new Person("Alex", 12)
p1.getTime()
装饰器工厂
装饰器工厂是一个返回装饰器函数,可以为装饰器添加参数,可以更灵活地控制装饰器的行为。
需求:定义一个LogInfo 类装饰器工厂,实现
Person
实例可以调用到introduce
方法,且introduce
中输出内容的次数,由LogInfo
接收的参数决定。
interface Person {
introduce: () => void
}
// 定义一个装饰器工厂LogInfo,它接受一个参数n,返回一个类装饰器
function LogInfo(n: number) {
// 装饰器函数,target 是被装饰的类
return function (target: Function) {
target.prototype.introduce = function () {
for (let i = 0; i < n; i++) {
console.log(this.name)
}
}
}
}
@LogInfo(2)
class Person {
constructor(
public name: string,
public age: number
) { }
speak() {
console.log("hello")
}
}
const p = new Person("alex", 11);
p.introduce()
装饰器组合
装饰器可以组合使用,执行顺序为:先【由上到下】的执行所有的装饰器工厂
,依次获取到装饰器,然后获取到装饰器,然后再【由下到上】执行所有的装饰器。
// 装饰器
function test1(target: Function) {
console.log("test1")
}
// 装饰器工厂
function test2() {
console.log("test2工厂")
return function (target: Function) {
console.log("test2")
}
}
// 装饰器工厂
function test3() {
console.log("test3工厂")
return function (target: Function) {
console.log("test3")
}
}
// 装饰器
function test4(target: Function) {
console.log("test4")
}
@test1
@test2()
@test3()
@test4
class Person {
constructor(
public name: string,
public age: number
) { }
}
const p = new Person("Alex", 12)
// test2工厂
// test3工厂
// test4
// test3
// test2
// test1
属性装饰器
基本语法
function Print(target: object, propertyKey: string) {
console.log(target, propertyKey)
}
class Person {
@Print
name: string
@Print
age: number
@Print
static grender:string
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new Person("Alex", 11)
- target:对于静态属性来说是类,对于实例属性来说是类的原型对象。
- propertyKey:属性名。
属性遮蔽
如下代码:当构造器中的
this.age = age
试图在实例上复制时,实际上调用了原型上age
属性的set方法
class Person {
name: string
age: number
constructor(
name: string,
age: number
) {
console.log("触发了")
this.name = name
this.age = age;
}
}
let value = 99
Object.defineProperty(Person.prototype, "age", {
get() {
return value
},
set(val) {
value = val
}
})
const p1 = new Person("Tom", 10)
console.log(p1, "p1")
应用举例
定义一个
State
装饰器,依赖监听属性的修改
function State(target: object, propertyKey: string) {
let key = `__${propertyKey}`
Object.defineProperty(target, propertyKey, {
get() {
console.log("get ==>", this[key])
return this[key]
},
set(val) {
console.log("set ==>", val)
this[key] = val
}
})
}
class Person {
name: string
@State
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p1 = new Person("alex", 12)
p1.age = 13
方法装饰器
基本语法
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(desc)
}
class Person{
constructor(
public name:string,
public age:number
){}
@Demo speak(){}
@Demo static print(n:number){
return n > 16
}
}
- target:对于静态方法来说值是类,对于实例方法来说值是原型对象。
- property:方法的名称。
- descriptor:方法的描述对象,其中Value属性是被装饰的方法。
应用举例
需求:
- 定义一个
Logger
方法装饰器,用于方法执行前和执行后,均追加一些额外逻辑。- 定义一个
Validate
方法装饰器,用于验证数据。
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
const originnal = descriptor.value;
descriptor.value = function (...args: []) {
console.log("propertyKey 开始执行")
const res = originnal.call(this, ...args)
console.log("propertyKey 执行结束")
return res;
}
}
function Validate(max: number) {
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
const originnal = descriptor.value;
descriptor.value = function (...args: any[]) {
if (args[0] > max) {
throw new Error("你年龄非法")
}
return originnal.apply(this, args)
}
}
}
class Person {
constructor(
public name: string,
public age: number
) { }
@Logger speak() {
console.log(`我叫:${this.name},今年${this.age}岁`)
}
@Validate(10) setAge(age: number) {
console.log(age)
this.age = age
}
}
const p = new Person("Alex", 12)
p.speak()
p.setAge(9)
console.log(p)
访问器装饰器
基本使用
function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Preson {
@Demo
get address() {
return "云南省-昆明市"
}
@Demo
static get country() {
return "China"
}
}
- target:对于静态方法来说值是类,对于实例方法来说值是原型对象。
- property:方法的名称。
- descriptor:方法的描述对象,其中Value属性是被装饰的方法。
应用举例
需求:对
Weather
类的temp
属性的set
访问器进行限制,设置最低温度-50,最高温度 50
function RangeValidate(min: number, max: number) {
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
const origin = descriptor.set;
descriptor.set = function (value: number) {
if (value < min || value > max) {
throw new Error(`${propertyKey}的值应该在${min}到${max}之间`)
}
if (origin) {
origin.call(this, value)
}
}
}
}
class Weather {
private _temp: number;
constructor(_temp: number) {
this._temp = _temp
}
get temp() {
return this._temp
}
@RangeValidate(-50, 50)
set temp(value) {
this._temp = value
}
}
const w = new Weather(10)
console.log(w.temp)
w.temp = 100
参数装饰器
基本语法
function Demo(target:object,propertyKey:string,paramterIndex:number){
}
class Person {
constructor(public name:string){}
speak(@Demo a:any,b:any){
}
}
- target:
- 如果修饰的是【实例方法】的参数,target 是类的【原型对象】
- 如果修饰的是【静态方法】的参数,target 是【类】
- property:参数所在方法的名称。
- descriptor:参数在函数参数列表中的索引,从0开始。