TS装饰器

在 TypeScript 中,装饰器(Decorators)是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上,用来修改类的行为。装饰器提供了一种方式来在不修改原有代码结构的基础上,对类及其成员进行扩展和增强。下面将从基本概念、装饰器类型、装饰器工厂、元数据等方面详细介绍 TypeScript 装饰器。

基本概念

装饰器本质上是一个函数,它接收不同数量的参数,具体取决于它所装饰的目标类型。装饰器可以修改目标的行为,例如添加额外的属性、修改方法的实现等。在 TypeScript 中使用装饰器需要在 tsconfig.json 中开启 experimentalDecorators 选项:

json 复制代码
{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

装饰器类型

1. 类装饰器

类装饰器应用于类的构造函数,可用于修改类的构造函数或类的原型。类装饰器接收一个参数,即类的构造函数。

typescript 复制代码
function logClass(constructor: Function) {
    console.log(`Class ${constructor.name} is created.`);
}

@logClass
class MyClass {
    constructor() {}
}

在上述代码中,logClass 是一个类装饰器,它会在 MyClass 类创建时输出一条日志。

2. 方法装饰器

方法装饰器应用于类的方法,接收三个参数:目标对象、方法名和属性描述符。可以用于修改方法的行为,例如添加日志、验证输入等。

typescript 复制代码
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}`);
        const result = originalMethod.apply(this, args);
        console.log(`Method ${propertyKey} returned: ${result}`);
        return result;
    };
    return descriptor;
}

class Calculator {
    @logMethod
    add(a: number, b: number) {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(1, 2);

在上述代码中,logMethod 是一个方法装饰器,它会在调用 add 方法前后输出日志。

3. 属性装饰器

属性装饰器应用于类的属性,接收两个参数:目标对象和属性名。属性装饰器主要用于记录属性的元数据或修改属性的行为。

typescript 复制代码
function readonly(target: any, propertyKey: string) {
    const descriptor: PropertyDescriptor = {
        writable: false
    };
    Object.defineProperty(target, propertyKey, descriptor);
}

class Person {
    @readonly
    name: string = 'John';
}

const person = new Person();
// 下面这行代码会报错,因为 name 属性是只读的
// person.name = 'Jane'; 

在上述代码中,readonly 是一个属性装饰器,它将 name 属性设置为只读。

4. 参数装饰器

参数装饰器应用于类方法的参数,接收三个参数:目标对象、方法名和参数索引。参数装饰器主要用于记录参数的元数据。

typescript 复制代码
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
    console.log(`Parameter at index ${parameterIndex} of method ${propertyKey} is logged.`);
}

class Example {
    method(@logParameter param: string) {}
}

const example = new Example();
example.method('test');

在上述代码中,logParameter 是一个参数装饰器,它会在调用 method 方法时输出参数的索引信息。

装饰器工厂

装饰器工厂是一个函数,它返回一个装饰器。通过装饰器工厂,可以在使用装饰器时传递参数。

typescript 复制代码
function logWithMessage(message: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            console.log(`${message} - Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}`);
            const result = originalMethod.apply(this, args);
            console.log(`${message} - Method ${propertyKey} returned: ${result}`);
            return result;
        };
        return descriptor;
    };
}

class AnotherCalculator {
    @logWithMessage('Custom log message')
    multiply(a: number, b: number) {
        return a * b;
    }
}

const anotherCalc = new AnotherCalculator();
anotherCalc.multiply(2, 3);

在上述代码中,logWithMessage 是一个装饰器工厂,它接收一个参数 message,并返回一个方法装饰器。

装饰器执行顺序

  • 多个装饰器应用于同一个目标时:装饰器的执行顺序是从下往上(从靠近目标的装饰器开始执行),从内往外(如果有装饰器工厂,先执行装饰器工厂返回的装饰器)。
typescript 复制代码
function decorator1() {
    console.log('Decorator 1 factory');
    return function (target: any) {
        console.log('Decorator 1');
    };
}

function decorator2() {
    console.log('Decorator 2 factory');
    return function (target: any) {
        console.log('Decorator 2');
    };
}

@decorator1()
@decorator2()
class MyClass2 {}

上述代码的输出顺序为:

dart 复制代码
Decorator 2 factory
Decorator 1 factory
Decorator 2
Decorator 1

元数据

在 TypeScript 中,可以使用 reflect-metadata 库来处理装饰器的元数据。元数据可以用于存储和获取与类、方法、属性或参数相关的额外信息。

typescript 复制代码
import 'reflect-metadata';

const METADATA_KEY = 'exampleMetadata';

function addMetadata(value: any) {
    return function (target: any, propertyKey: string) {
        Reflect.defineMetadata(METADATA_KEY, value, target, propertyKey);
    };
}

function getMetadata(target: any, propertyKey: string) {
    return Reflect.getMetadata(METADATA_KEY, target, propertyKey);
}

class MyClass3 {
    @addMetadata('Some metadata')
    myProperty: string;
}

const instance = new MyClass3();
const metadata = getMetadata(instance, 'myProperty');
console.log(metadata); // 输出: Some metadata

在上述代码中,使用 reflect-metadata 库的 Reflect.defineMetadataReflect.getMetadata 方法来定义和获取元数据。

总结

TypeScript 装饰器提供了一种强大的方式来扩展和修改类及其成员的行为。通过不同类型的装饰器和装饰器工厂,可以实现各种功能,如日志记录、权限验证、只读属性等。同时,结合 reflect-metadata 库可以处理装饰器的元数据,进一步增强装饰器的功能。

相关推荐
Perfect—完美4 分钟前
Vue 3 事件总线详解:构建组件间高效通信的桥梁
前端·javascript·vue.js
二川bro14 分钟前
模拟类似 DeepSeek 的对话
前端·人工智能
祈澈菇凉39 分钟前
Vue 中如何实现自定义指令?
前端·javascript·vue.js
sorryhc1 小时前
解读Ant Design X API流式响应和流式渲染的原理
前端·react.js·ai 编程
1024小神1 小时前
vue/react前端项目打包的时候加上时间,防止后端扯皮
前端·vue.js·react.js
拉不动的猪1 小时前
刷刷题35(uniapp中级实际项目问题-2)
前端·javascript·面试
bigcarp1 小时前
理解langchain langgraph 官方文档示例代码中的MemorySaver
java·前端·langchain
FreeCultureBoy1 小时前
从 VS Code 的插件市场下载扩展插件
前端
前端菜鸟日常2 小时前
Webpack 和 Vite 的主要区别
前端·webpack·node.js
仙魁XAN2 小时前
Flutter 学习之旅 之 flutter 在设备上进行 全面屏 设置/隐藏状态栏/隐藏导航栏 设置
前端·学习·flutter