ts-装饰器
装饰器是一种特殊的函数,可以对类、方法、属性、参数进行扩展
装饰器目前是实验性特性,需要手动调整配置开启装饰器
// tsconfig.json文件中,将该行注释打开
"experimentalDecorators": true,
类装饰器
类装饰器是⼀个应⽤在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑
基本使用
// 装饰器会在类定义的时候执行
// target表示被装饰的类
function ClassString(target:Function){
// 在被装饰的类的原型上添加getInfo方法
target.prototype.getInfo = function (){
return "info"
}
// 在被装饰的类的原型上添加toString方法
target.prototype.toString = function (){
return JSON.stringify(this)
}
}
@ClassString
class User{
constructor() {
}
}
const user = new User()
// toString是 js有的内置方法,可以直接调用
user.toString();
// getInfo是自定义的方法,TypeScript 编译器在静态类型检查时不知道 User 类有 getInfo 方法
// 虽然装饰器在运行时向类的原型添加了这个方法,但 TypeScript 的静态类型系统并不自动识别这种动态添加的方法
// user 对象断言为 any 类型 绕过编译器的检查
(user as any).getInfo()
// 或者可以@ts-ignore忽略对应的代码检查,可以使用
// @ts-ignore
user.getInfo()
类装饰器的返回值
类装饰器有返回值:若类装饰器返回⼀个新的类,那这个新类将替换掉被装饰的类
function Test(target:Function){
// 装饰器有返回值时,该返回值会替换掉被装饰的类
return class {
constructor() {
console.log("test")
}
}
}
@Test
class User{
constructor() {
console.log("user")
}
}
// user类会变替换为test类
const user = new User()
构造类型
在 TypeScript 中, Function 类型所表示的范围⼗分⼴泛,包括:普通函数、箭头函数、⽅ 法等等
但并⾮Function 类型的函数都可以被 new 关键字实例化,例如箭头函数是不能 被实例化的
- 声明构造类型
// new 表示:该类型是可以⽤new操作符调⽤。
// ...args 表示:构造器可以接受【任意数量】的参数。
// any[] 表示:构造器可以接受【任意类型】的参数。
// {} 表示:返回类型是对象(⾮null、⾮undefined的对象)
// 定义类的类型,涵义是构造类型
type ClassType = new (...args: any[]) => {};
function Test(target:ClassType){
}
@Test
class User{}
- 声明构造类型、静态属性
type ClassType = {
new(...args: any[]): {}; // 构造签名
age: number // 静态属性
}
// 被装饰的类类型ClassType
function Test(target:ClassType){
console.log("1")
}
@Test
class User{
static age:number
}
替换被装饰的类
type Constructor = new (...args:any[]) => {}
// 装饰器 target是Constructor约束的泛型
function LogTime<T extends Constructor>(target:T){
// 新类继承自被装饰的类,将被继承的类替换为新类
return class extends target{
createdTime:Date
constructor(...args:any[]) {
super(...args);
this.createdTime = new Date()
}
getTime(){
return `对象创建时间${this.createdTime}`
}
}
}
@LogTime
class User{
constructor(public a:string,public b:string) {
}
getInfo(){
console.log(this.a,this.b)
}
}
const user = new User("1","2")
// @ts-ignore
console.log(`对象的创建时间${user.getTime()}`)
// 装饰器虽然替换了新类,但是继承了target所以可以继续使用target的方法
user.getInfo()
装饰器工厂
装饰器⼯⼚是⼀个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的⾏为
// 定义一个装饰器工厂,即函数,可以传参,返回一个装饰器
function LogInfo(count: number) {
// 返回的装饰器,target是被装饰的类
return function (target: Function) {
target.prototype.getInfo = function (target: Function) {
for (let i = 0; i < count; i++){
console.log(this.a,this.b)
}
}
}
}
@LogInfo(3) // 等同于 LogInfo(3)(User)
class User{
constructor(public a:string,public b:string) {
}
}
const user = new User("1","2")
装饰器组合
装饰器可以组合使⽤,执⾏顺序为:先由上到下的执⾏所有的装饰器⼯⼚,依次获取到装饰器,然后再由下到上执⾏所有的装饰器
// 装饰器
function test(t:Function){
console.log("test")
}
// 装饰器工厂
function test1(){
return function (t:Function){
console.log("test1")
}
}
// 装饰器
function test2(t:Function){
console.log("test2")
}
// 装饰器工厂
function test3(){
return function (t:Function){
console.log("test3")
}
}
@test
@test1()
@test2
@test3()
class User{
}
// 执行顺序为test3、test2、test1、test
const user = new User()
属性装饰器
基本使用
// target:对静态属性来说值是类,对于实例属性来说值是类的原型对象
// propertyKey:属性名
function Test(target:object,propertyKey:string){
console.log(target,propertyKey)
}
class User{
// 装饰属性
@Test age:string
constructor(age:string) {
this.age = age
}
}
const user = new User("1")
属性遮蔽
指内部作用域中的属性定义覆盖了外部作用域中同名属性的现象。这种情况在类(Class)、接口(Interface)和嵌套对象等场景下较为常见
class Parent {
public property: string = 'Parent Value';
}
class Child extends Parent {
public property: string = 'Child Value';
public getParentProperty(): string {
return super.property;
}
}
let child = new Child();
console.log(child.property); // 输出: Child Value
/*
Child类中的property属性遮蔽了Parent类中的property属性。
直接访问child.property获取的是Child类自身定义的property的值。
通过super.property可以访问到被遮蔽的Parent类中的property
*/
监视属性案例
function State(target:object,propertyKey:string){
// 存储属性的内部值
let key = `__${propertyKey}`
// 使用Object.defineProperty替换的类的原始属性,重新定义属性使用其自定义的getter和settr
Object.defineProperty(target,propertyKey,{
get(){
return this[key]
},
set(newValue){
this[key] = newValue
}
}
)
}
class User {
name:string
@State age:number
constructor(name:string,age:number) {
this.name = name
this.age = age
}
}
const u1 = new User("1",18)
const u2 = new User("2",28)
u1.age = 180
u2.age = 280
console.log(u1.age)
console.log(u2.age)
方法装饰器
基本使用
// target: 对于静态⽅法来说值是类,对于实例⽅法来说值是原型对象
// propertyKey:⽅法的名称
// descriptor: ⽅法的描述对象,PropertyDescriptor参数包含了与对象属性相关的配置信息,如configurable(是否可配置)、 enumerable(是否可枚举)、value(属性的值,对于方法来说是函数体、方法本身)、writable(对于方法来说,这个属性通常不太相关,因为方法主要是被调用而不是被赋值)、get和set(主要用于属性的存取控制,对于方法不太常用,但在某些特殊场景下可能会有作用)
function Test(target: object, propertyKey: string, descriptor: PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class User{
constructor(public name:string) {
}
// 装饰实例方法
@Test info(){
console.log(this.name)
}
// 装饰静态方法
@Test static hello(){
console.log(this.name,"hello")
}
}
const user = new User("a")
逻辑追加&数据验证案例
// 装饰器工场
function Validate(maxValue: number) {
// 装饰器
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始方法
const original = descriptor.value
// 替换原始方法
descriptor.value = function (...args: any[]) {
// 自定义校验逻辑
if (args[0] > maxValue) {
throw new Error(args[0])
}
// 如果参数符合要求 调用原始方法
return original.apply(this, args)
}
}
}
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始⽅法
const original = descriptor.value;
// 替换原始⽅法
descriptor.value = function (...args: any[]) {
console.log("开始执行逻辑")
const result = original.call(this, ...args)
console.log("逻辑执行结束")
return result
}
}
class User{
constructor(public age:number,public name:string) {
}
@Validate(18)
age_info(){
console.log(this.age)
}
@Logger
name_info(){
console.log(this.name)
}
}
访问器装饰器
// target:对于实例访问器来说值是所属类的原型对象,对于静态访问器来说值是所属类
// propertyKey:访问器的名称
// descriptor: 描述对象
function Test(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class User{
@Test
get age(){
return 18
}
@Test
static get num(){
return 10
}
}
function Valid(min:number,max:number){
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor){
// 保存原始的 setter ⽅法,以便在后续调⽤中使⽤
const originalSetter = descriptor.set
// 重写setter方法,加入范围验证
descriptor.set = function (value:number){
if (value <min || value > min){
throw new Error(`${value}`)
}
// 如果值在范围内,且原始 setter ⽅法存在,则调⽤原始 setter ⽅法
if (originalSetter){
originalSetter.call(this,value)
}
}
}
}
class User{
private _age:number
constructor(_age:number) {
this._age = _age
}
@Valid(-10,10)
set age(value:number){
this._age = value
}
get age(){
return this._age
}
}
参数装饰器
// target:如果修饰的是实例⽅法的参数,target 是类的原型对象,如果修饰的是静态⽅法的参数,target是类。
// propertyKey:参数所在的⽅法的名称。
// parameterIndex: 参数在函数参数列表中的索引,从 0 开始。
function Param(target: object, propertyKey: string, parameterIndex: number){
}
class User{
constructor(public name:string) {
}
info(@Param age:number){
console.log(this.name,age)
}
}
风月都好看,人间也浪漫.