MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript类装饰器的应用场景与实现技巧

2023-03-202.9k 阅读

TypeScript类装饰器的基本概念

在深入探讨TypeScript类装饰器的应用场景与实现技巧之前,我们先来回顾一下其基本概念。装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问器、属性或参数上。类装饰器应用于类的构造函数,可以用来监视、修改或替换类定义。

类装饰器表达式会在运行时当作函数被调用,它的唯一参数就是被装饰类的构造函数。例如:

function classDecorator(target: Function) {
    console.log('类装饰器被调用,target 是类的构造函数:', target);
}

@classDecorator
class MyClass {
    constructor() {
        console.log('MyClass 实例被创建');
    }
}

在上述代码中,classDecorator 是一个类装饰器,当 MyClass 类被定义时,classDecorator 函数就会被调用,并且传入 MyClass 的构造函数作为参数。

类装饰器的应用场景

日志记录

在开发过程中,日志记录是非常重要的一环,它可以帮助我们追踪程序的执行流程,排查错误。通过类装饰器,我们可以方便地为类的所有方法添加日志记录功能。

function log(target: Function) {
    const originalConstructor = target;

    return class extends originalConstructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`实例化 ${originalConstructor.name}`);
        }

        // 遍历类的所有方法,并为其添加日志记录
        [key: string]: any;
        ngOnInit() {
            for (const methodName in this) {
                const method = this[methodName];
                if (typeof method === 'function') {
                    this[methodName] = function (...args: any[]) {
                        console.log(`调用方法 ${methodName}`);
                        return method.apply(this, args);
                    };
                }
            }
        }
    };
}

@log
class AppComponent {
    ngOnInit() {
        console.log('AppComponent 初始化');
    }

    someMethod() {
        console.log('执行 someMethod');
    }
}

const app = new AppComponent();
app.ngOnInit();
app.someMethod();

在上述代码中,log 装饰器创建了一个新的类,这个类继承自原类,并在构造函数中打印实例化信息。同时,在 ngOnInit 方法中,遍历原类的所有方法,并为它们添加日志记录功能,这样当调用类的方法时,就会打印方法名。

依赖注入

依赖注入是一种软件设计模式,它允许将对象的依赖关系从对象内部转移到外部,通过构造函数或属性注入。类装饰器可以用来实现简单的依赖注入机制。

interface Logger {
    log(message: string): void;
}

class ConsoleLogger implements Logger {
    log(message: string) {
        console.log(message);
    }
}

interface UserService {
    getUserName(): string;
}

class DefaultUserService implements UserService {
    getUserName() {
        return 'defaultUser';
    }
}

function injectDependencies(target: Function) {
    const originalConstructor = target;
    return class extends originalConstructor {
        logger: Logger;
        userService: UserService;

        constructor(...args: any[]) {
            super(...args);
            this.logger = new ConsoleLogger();
            this.userService = new DefaultUserService();
        }
    };
}

@injectDependencies
class HomeComponent {
    constructor() { }

    showInfo() {
        this.logger.log(`当前用户: ${this.userService.getUserName()}`);
    }
}

const home = new HomeComponent();
home.showInfo();

在这个例子中,injectDependencies 装饰器为 HomeComponent 类注入了 LoggerUserService 的实例,使得 HomeComponent 类不需要自己创建这些依赖对象,从而降低了类之间的耦合度。

性能监测

在优化应用程序性能时,了解每个方法的执行时间是非常有帮助的。我们可以使用类装饰器来为类的方法添加性能监测功能。

function performanceMonitor(target: Function) {
    const originalConstructor = target;
    return class extends originalConstructor {
        constructor(...args: any[]) {
            super(...args);
        }

        [key: string]: any;
        ngOnInit() {
            for (const methodName in this) {
                const method = this[methodName];
                if (typeof method === 'function') {
                    this[methodName] = function (...args: any[]) {
                        const start = Date.now();
                        const result = method.apply(this, args);
                        const end = Date.now();
                        console.log(`${methodName} 执行时间: ${end - start} ms`);
                        return result;
                    };
                }
            }
        }
    };
}

@performanceMonitor
class MathService {
    add(a: number, b: number) {
        return a + b;
    }

    multiply(a: number, b: number) {
        return a * b;
    }
}

const mathService = new MathService();
mathService.ngOnInit();
mathService.add(2, 3);
mathService.multiply(4, 5);

在上述代码中,performanceMonitor 装饰器为 MathService 类的所有方法添加了性能监测逻辑,每次调用方法时,都会打印方法的执行时间,方便我们找出性能瓶颈。

类装饰器的实现技巧

装饰器工厂

有时候,我们希望装饰器能够接受参数,这时候就需要使用装饰器工厂。装饰器工厂是一个返回装饰器的函数。

function loggingPrefix(prefix: string) {
    return function (target: Function) {
        const originalConstructor = target;
        return class extends originalConstructor {
            constructor(...args: any[]) {
                super(...args);
                console.log(`${prefix} 实例化 ${originalConstructor.name}`);
            }

            [key: string]: any;
            ngOnInit() {
                for (const methodName in this) {
                    const method = this[methodName];
                    if (typeof method === 'function') {
                        this[methodName] = function (...args: any[]) {
                            console.log(`${prefix} 调用方法 ${methodName}`);
                            return method.apply(this, args);
                        };
                    }
                }
            }
        };
    };
}

@loggingPrefix('DEBUG: ')
class AnotherComponent {
    ngOnInit() {
        console.log('AnotherComponent 初始化');
    }

    doSomething() {
        console.log('执行 doSomething');
    }
}

const another = new AnotherComponent();
another.ngOnInit();
another.doSomething();

在这个例子中,loggingPrefix 是一个装饰器工厂,它接受一个 prefix 参数,并返回一个真正的类装饰器。通过这种方式,我们可以根据不同的参数创建不同的装饰器。

组合装饰器

在实际应用中,我们可能需要为一个类应用多个装饰器。TypeScript 支持将多个装饰器应用到同一个类上,它们会按照从下到上(或者说从内到外)的顺序执行。

function log(target: Function) {
    const originalConstructor = target;
    return class extends originalConstructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`实例化 ${originalConstructor.name}`);
        }

        [key: string]: any;
        ngOnInit() {
            for (const methodName in this) {
                const method = this[methodName];
                if (typeof method === 'function') {
                    this[methodName] = function (...args: any[]) {
                        console.log(`调用方法 ${methodName}`);
                        return method.apply(this, args);
                    };
                }
            }
        }
    };
}

function performanceMonitor(target: Function) {
    const originalConstructor = target;
    return class extends originalConstructor {
        constructor(...args: any[]) {
            super(...args);
        }

        [key: string]: any;
        ngOnInit() {
            for (const methodName in this) {
                const method = this[methodName];
                if (typeof method === 'function') {
                    this[methodName] = function (...args: any[]) {
                        const start = Date.now();
                        const result = method.apply(this, args);
                        const end = Date.now();
                        console.log(`${methodName} 执行时间: ${end - start} ms`);
                        return result;
                    };
                }
            }
        }
    };
}

@log
@performanceMonitor
class ComplexComponent {
    ngOnInit() {
        console.log('ComplexComponent 初始化');
    }

    someAction() {
        console.log('执行 someAction');
    }
}

const complex = new ComplexComponent();
complex.ngOnInit();
complex.someAction();

在上述代码中,ComplexComponent 类同时应用了 logperformanceMonitor 装饰器。首先 performanceMonitor 装饰器会对类进行处理,然后 log 装饰器再对经过 performanceMonitor 处理后的类进行处理。这样,这个类就同时具备了日志记录和性能监测的功能。

装饰器与继承

当使用装饰器的类被继承时,需要注意一些细节。装饰器只会应用到被装饰的类本身,而不会自动应用到子类。但是,我们可以通过一些技巧在子类中复用父类的装饰器逻辑。

function log(target: Function) {
    const originalConstructor = target;
    return class extends originalConstructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`实例化 ${originalConstructor.name}`);
        }

        [key: string]: any;
        ngOnInit() {
            for (const methodName in this) {
                const method = this[methodName];
                if (typeof method === 'function') {
                    this[methodName] = function (...args: any[]) {
                        console.log(`调用方法 ${methodName}`);
                        return method.apply(this, args);
                    };
                }
            }
        }
    };
}

@log
class BaseComponent {
    ngOnInit() {
        console.log('BaseComponent 初始化');
    }

    baseMethod() {
        console.log('执行 baseMethod');
    }
}

class SubComponent extends BaseComponent {
    ngOnInit() {
        super.ngOnInit();
        console.log('SubComponent 初始化');
    }

    subMethod() {
        console.log('执行 subMethod');
    }
}

const sub = new SubComponent();
sub.ngOnInit();
sub.baseMethod();
sub.subMethod();

在这个例子中,BaseComponent 类应用了 log 装饰器,而 SubComponent 类继承自 BaseComponent。虽然 SubComponent 没有直接应用 log 装饰器,但由于它继承了 BaseComponent,所以在一定程度上复用了 log 装饰器的逻辑,比如实例化时的日志记录。不过对于 subMethod 方法,并没有自动添加日志记录,如果需要为 subMethod 也添加相同的日志记录逻辑,我们可以在 SubComponentngOnInit 方法中手动添加类似的逻辑。

class SubComponent extends BaseComponent {
    ngOnInit() {
        super.ngOnInit();
        console.log('SubComponent 初始化');
        for (const methodName in this) {
            const method = this[methodName];
            if (typeof method === 'function' && methodName!== 'ngOnInit') {
                this[methodName] = function (...args: any[]) {
                    console.log(`调用方法 ${methodName}`);
                    return method.apply(this, args);
                };
            }
        }
    }

    subMethod() {
        console.log('执行 subMethod');
    }
}

这样,SubComponent 的所有方法(除了 ngOnInit,因为 ngOnInit 已经在父类的 ngOnInit 中处理过)就都具备了日志记录功能。

注意事项

装饰器的兼容性

虽然 TypeScript 支持装饰器,但装饰器目前是一项实验性的特性,不同的运行环境可能对其支持程度不同。在使用装饰器时,需要确保目标运行环境(如浏览器、Node.js 等)能够正确解析和执行装饰器代码。通常,我们需要使用 Babel 等工具将包含装饰器的 TypeScript 代码转换为目标环境支持的 JavaScript 代码。

装饰器对类结构的影响

当使用装饰器对类进行修改时,需要注意装饰器可能会改变类的结构和行为。例如,在上述的一些例子中,装饰器通过创建新的类并继承原类来实现功能扩展,这可能会影响到类的原型链和一些基于原型链的操作。在编写装饰器和使用被装饰的类时,要充分考虑这些潜在的影响,以避免出现意外的错误。

装饰器的性能

虽然装饰器为我们提供了强大的功能,但在应用装饰器时,尤其是为类的大量方法添加装饰逻辑时,可能会对性能产生一定的影响。例如,为每个方法添加日志记录或性能监测逻辑,会增加方法调用的开销。在实际应用中,需要权衡装饰器带来的便利性和性能损耗,根据具体的业务场景和性能要求来决定是否使用装饰器以及如何使用。

通过深入理解 TypeScript 类装饰器的应用场景与实现技巧,我们可以在开发过程中更加灵活地使用装饰器,提高代码的可维护性、可扩展性和复用性。无论是日志记录、依赖注入还是性能监测等方面,装饰器都能为我们提供优雅的解决方案。同时,在使用过程中要注意装饰器的兼容性、对类结构的影响以及性能等问题,确保代码在各种环境下都能稳定高效地运行。