|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
TypeScript作为JavaScript的超集,为开发者提供了静态类型检查和更强大的面向对象编程能力。在TypeScript的众多特性中,装饰器(Decorators)是一个极具潜力的元编程特性,它允许我们在不修改原有代码结构的情况下,动态地为类、方法、属性或参数添加额外的行为。装饰器不仅可以提高代码的可读性和可维护性,还能显著提升开发效率,是现代TypeScript项目中不可或缺的利器。
本文将深入探讨TypeScript装饰器的核心概念、实际应用场景以及实战技巧,帮助开发者充分利用装饰器来提升代码质量与开发效率。
TypeScript装饰器基础
什么是装饰器
装饰器是一种特殊类型的声明,它可以附加到类声明、方法、访问符、属性或参数上。装饰器使用@expression的形式,其中expression必须是一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。
本质上,装饰器是一个函数,它接收目标对象(类、方法、属性等)作为参数,并可以对其进行修改或扩展。这种模式被称为元编程,即编写可以操作程序的程序。
装饰器的工作原理
装饰器在TypeScript中的工作原理可以简化为以下几个步骤:
1. 定义装饰器函数
2. 使用@符号将装饰器应用到目标上
3. 在编译或运行时,装饰器函数会被调用,并接收相关信息作为参数
4. 装饰器函数可以修改或扩展目标的行为
要启用装饰器支持,需要在tsconfig.json文件中设置experimentalDecorators选项为true:
- {
- "compilerOptions": {
- "experimentalDecorators": true,
- "emitDecoratorMetadata": true
- }
- }
复制代码
装饰器的类型
TypeScript支持多种类型的装饰器,每种类型都有其特定的应用场景和参数结构。
类装饰器
类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。类装饰器接收一个参数:类的构造函数。
- function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
- return class extends constructor {
- newProperty = "new property";
- hello = "override";
- }
- }
- @classDecorator
- class Greeter {
- property = "property";
- hello: string;
- constructor(m: string) {
- this.hello = m;
- }
- }
- console.log(new Greeter("world"));
复制代码
方法装饰器
方法装饰器应用于方法定义上,可以用来监视、修改或替换方法定义。方法装饰器接收三个参数:
• 对于静态成员,是类的构造函数;对于实例成员,是类的原型
• 成员的名字
• 成员的属性描述符
- function methodDecorator(
- target: any,
- propertyKey: string,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`Method ${propertyKey} called with args: ${args}`);
- const result = originalMethod.apply(this, args);
- console.log(`Method ${propertyKey} returned: ${result}`);
- return result;
- };
-
- return descriptor;
- }
- class Calculator {
- @methodDecorator
- add(a: number, b: number): number {
- return a + b;
- }
- }
- const calculator = new Calculator();
- calculator.add(2, 3);
复制代码
属性装饰器
属性装饰器应用于属性声明上,可以用来监视或修改属性的定义。属性装饰器接收两个参数:
• 对于静态成员,是类的构造函数;对于实例成员,是类的原型
• 成员的名字
- function propertyDecorator(target: any, propertyKey: string) {
- let value: string;
-
- const getter = function() {
- console.log(`Getting value for ${propertyKey}`);
- return value;
- };
-
- const setter = function(newVal: string) {
- console.log(`Setting value for ${propertyKey} to ${newVal}`);
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- }
- class User {
- @propertyDecorator
- public name: string;
-
- constructor(name: string) {
- this.name = name;
- }
- }
- const user = new User("John Doe");
- console.log(user.name);
- user.name = "Jane Smith";
复制代码
参数装饰器
参数装饰器应用于参数声明上,可以用来监视参数的定义。参数装饰器接收三个参数:
• 对于静态成员,是类的构造函数;对于实例成员,是类的原型
• 成员的名字
• 参数在函数参数列表中的索引
- function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
- console.log(`Parameter decorator applied to ${propertyKey} at index ${parameterIndex}`);
- }
- class UserService {
- getUserById(@parameterDecorator id: number) {
- console.log(`Fetching user with ID: ${id}`);
- return { id, name: "John Doe" };
- }
- }
- const userService = new UserService();
- userService.getUserById(123);
复制代码
实际项目应用场景
装饰器在实际项目中有广泛的应用场景,下面我们将探讨几个常见的应用场景,并提供详细的代码示例。
日志记录
日志记录是应用程序开发中的重要环节,装饰器可以优雅地实现日志功能,而不需要在业务逻辑中掺杂日志代码。
- function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- const startTime = Date.now();
- console.log(`[${new Date().toISOString()}] Calling ${propertyKey} with args:`, args);
-
- try {
- const result = originalMethod.apply(this, args);
- const endTime = Date.now();
- console.log(`[${new Date().toISOString()}] Method ${propertyKey} completed in ${endTime - startTime}ms`);
- return result;
- } catch (error) {
- console.error(`[${new Date().toISOString()}] Method ${propertyKey} threw error:`, error);
- throw error;
- }
- };
-
- return descriptor;
- }
- class ProductService {
- @log
- getProduct(id: number) {
- // 模拟数据库查询
- if (id <= 0) {
- throw new Error("Invalid product ID");
- }
- return { id, name: `Product ${id}`, price: 100 };
- }
-
- @log
- saveProduct(product: any) {
- // 模拟保存产品
- console.log("Saving product to database...");
- return { ...product, id: Math.floor(Math.random() * 1000) };
- }
- }
- const productService = new ProductService();
- productService.getProduct(123);
- productService.saveProduct({ name: "New Product", price: 200 });
- try {
- productService.getProduct(-1);
- } catch (error) {
- console.log("Caught expected error:", error.message);
- }
复制代码
权限控制
在许多应用中,需要对某些方法或端点进行权限控制,装饰器可以很方便地实现这一功能。
- // 定义角色类型
- type Role = 'admin' | 'user' | 'guest';
- // 用户上下文
- interface UserContext {
- id: number;
- username: string;
- roles: Role[];
- }
- // 当前用户上下文(在实际应用中可能来自JWT或会话)
- const currentUser: UserContext = {
- id: 1,
- username: 'johndoe',
- roles: ['user']
- };
- // 权限装饰器工厂
- function requireRole(...requiredRoles: Role[]) {
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- // 检查用户是否有所需角色
- const hasRequiredRole = requiredRoles.some(role =>
- currentUser.roles.includes(role)
- );
-
- if (!hasRequiredRole) {
- throw new Error(`Access denied. Required roles: ${requiredRoles.join(', ')}`);
- }
-
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- };
- }
- class AdminService {
- @requireRole('admin')
- deleteUser(userId: number) {
- console.log(`User ${userId} deleted by admin`);
- return { success: true };
- }
-
- @requireRole('admin', 'user')
- updateUserProfile(userId: number, profile: any) {
- console.log(`User ${userId} profile updated`);
- return { success: true };
- }
-
- @requireRole('guest')
- viewPublicContent() {
- console.log("Displaying public content");
- return { content: "This is public content" };
- }
- }
- const adminService = new AdminService();
- // 这些调用会成功,因为当前用户有'user'角色
- try {
- adminService.updateUserProfile(123, { name: "John" });
- adminService.viewPublicContent();
- } catch (error) {
- console.error("Error:", error.message);
- }
- // 这个调用会失败,因为当前用户没有'admin'角色
- try {
- adminService.deleteUser(456);
- } catch (error) {
- console.error("Error:", error.message);
- }
复制代码
数据验证
装饰器可以用于实现优雅的数据验证,特别是在处理API请求或表单数据时。
- // 定义验证规则接口
- interface ValidationRule {
- validate(value: any): boolean;
- message: string;
- }
- // 必填验证规则
- const required: ValidationRule = {
- validate: (value) => value !== undefined && value !== null && value !== '',
- message: 'This field is required'
- };
- // 最小长度验证规则
- function minLength(min: number): ValidationRule {
- return {
- validate: (value) => value.length >= min,
- message: `Minimum length is ${min}`
- };
- }
- // 邮箱格式验证规则
- const email: ValidationRule = {
- validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
- message: 'Invalid email format'
- };
- // 验证装饰器工厂
- function validate(...rules: ValidationRule[]) {
- return function(target: any, propertyKey: string) {
- let value: any;
-
- const getter = function() {
- return value;
- };
-
- const setter = function(newVal: any) {
- for (const rule of rules) {
- if (!rule.validate(newVal)) {
- throw new Error(`Validation failed for ${propertyKey}: ${rule.message}`);
- }
- }
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- };
- }
- class UserRegistration {
- @validate(required, minLength(3))
- username: string;
-
- @validate(required, email)
- email: string;
-
- @validate(required, minLength(8))
- password: string;
-
- constructor(username: string, email: string, password: string) {
- this.username = username;
- this.email = email;
- this.password = password;
- }
- }
- // 正确的注册
- try {
- const user1 = new UserRegistration("john_doe", "john@example.com", "password123");
- console.log("User registration successful:", user1);
- } catch (error) {
- console.error("Registration error:", error.message);
- }
- // 错误的注册 - 用户名太短
- try {
- const user2 = new UserRegistration("jd", "john@example.com", "password123");
- } catch (error) {
- console.error("Registration error:", error.message);
- }
- // 错误的注册 - 无效邮箱
- try {
- const user3 = new UserRegistration("john_doe", "invalid-email", "password123");
- } catch (error) {
- console.error("Registration error:", error.message);
- }
复制代码
依赖注入
依赖注入是现代软件开发中的一种重要设计模式,装饰器在实现依赖注入时非常有用。
- // 依赖容器
- class Container {
- private services: Map<string, any> = new Map();
-
- register<T>(name: string, implementation: new (...args: any[]) => T): void {
- this.services.set(name, implementation);
- }
-
- resolve<T>(name: string): T {
- const implementation = this.services.get(name);
- if (!implementation) {
- throw new Error(`Service ${name} not found`);
- }
- return new implementation();
- }
- }
- // 全局容器实例
- const container = new Container();
- // 依赖注入装饰器
- function inject(serviceName: string) {
- return function(target: any, propertyKey: string) {
- Object.defineProperty(target, propertyKey, {
- get() {
- return container.resolve(serviceName);
- },
- enumerable: true,
- configurable: true
- });
- };
- }
- // 定义服务
- class LoggerService {
- log(message: string) {
- console.log(`[LOG] ${new Date().toISOString()}: ${message}`);
- }
- }
- class DatabaseService {
- query(sql: string) {
- console.log(`Executing query: ${sql}`);
- return [{ id: 1, name: "Sample Data" }];
- }
- }
- // 注册服务
- container.register('logger', LoggerService);
- container.register('database', DatabaseService);
- // 使用依赖注入
- class UserService {
- @inject('logger')
- private logger!: LoggerService;
-
- @inject('database')
- private database!: DatabaseService;
-
- getUser(id: number) {
- this.logger.log(`Fetching user with ID: ${id}`);
- const results = this.database.query(`SELECT * FROM users WHERE id = ${id}`);
- return results[0];
- }
-
- createUser(userData: any) {
- this.logger.log(`Creating new user: ${JSON.stringify(userData)}`);
- this.database.query(`INSERT INTO users (name, email) VALUES ('${userData.name}', '${userData.email}')`);
- return { ...userData, id: Math.floor(Math.random() * 1000) };
- }
- }
- const userService = new UserService();
- const user = userService.getUser(1);
- console.log("Retrieved user:", user);
- const newUser = userService.createUser({ name: "John Doe", email: "john@example.com" });
- console.log("Created user:", newUser);
复制代码
性能监控
装饰器可以用于监控方法或函数的性能,帮助开发者识别性能瓶颈。
缓存机制
装饰器可以用于实现方法结果的缓存,避免重复计算或查询,提高性能。
- // 简单的内存缓存
- class SimpleCache {
- private cache: Map<string, { value: any; expiry: number }> = new Map();
-
- get(key: string): any | null {
- const item = this.cache.get(key);
- if (!item) return null;
-
- if (Date.now() > item.expiry) {
- this.cache.delete(key);
- return null;
- }
-
- return item.value;
- }
-
- set(key: string, value: any, ttlMs: number = 5000): void {
- this.cache.set(key, {
- value,
- expiry: Date.now() + ttlMs
- });
- }
-
- clear(): void {
- this.cache.clear();
- }
- }
- const cache = new SimpleCache();
- // 缓存装饰器工厂
- function cacheResult(ttlMs: number = 5000) {
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- // 生成缓存键
- const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
-
- // 尝试从缓存获取结果
- const cachedResult = cache.get(cacheKey);
- if (cachedResult !== null) {
- console.log(`[CACHE] Hit for ${propertyKey} with args: ${JSON.stringify(args)}`);
- return cachedResult;
- }
-
- // 缓存未命中,执行方法
- console.log(`[CACHE] Miss for ${propertyKey} with args: ${JSON.stringify(args)}`);
- const result = originalMethod.apply(this, args);
-
- // 存入缓存
- cache.set(cacheKey, result, ttlMs);
-
- return result;
- };
-
- return descriptor;
- };
- }
- class FibonacciService {
- @cacheResult(10000) // 缓存10秒
- fibonacci(n: number): number {
- console.log(`Computing fibonacci(${n})...`);
- if (n <= 1) return n;
- return this.fibonacci(n - 1) + this.fibonacci(n - 2);
- }
-
- @cacheResult()
- getUserData(userId: number) {
- console.log(`Fetching user data for ID: ${userId}`);
- // 模拟API调用
- return {
- id: userId,
- name: `User ${userId}`,
- email: `user${userId}@example.com`,
- timestamp: Date.now()
- };
- }
- }
- const fibonacciService = new FibonacciService();
- // 第一次调用会计算
- console.log("Result:", fibonacciService.fibonacci(10));
- // 第二次调用会从缓存获取
- console.log("Result:", fibonacciService.fibonacci(10));
- // 第一次调用用户数据
- console.log("User:", fibonacciService.getUserData(123));
- // 第二次调用用户数据(从缓存获取)
- console.log("User:", fibonacciService.getUserData(123));
- // 等待一段时间后再次调用
- setTimeout(() => {
- console.log("After timeout, User:", fibonacciService.getUserData(123));
- }, 6000);
复制代码
实战技巧与最佳实践
在使用TypeScript装饰器时,有一些技巧和最佳实践可以帮助我们更好地利用这一特性。
装饰器工厂模式
装饰器工厂是一个返回装饰器函数的函数,它允许我们向装饰器传递参数,使装饰器更加灵活和可配置。
- // 装饰器工厂示例
- function logWithPrefix(prefix: string) {
- // 返回实际的装饰器函数
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`[${prefix}] Starting ${propertyKey}`);
- const result = originalMethod.apply(this, args);
- console.log(`[${prefix}] Finished ${propertyKey}`);
- return result;
- };
-
- return descriptor;
- };
- }
- class ApiService {
- @logWithPrefix("API")
- fetchData(endpoint: string) {
- console.log(`Fetching data from ${endpoint}`);
- return { data: `Data from ${endpoint}` };
- }
-
- @logWithPrefix("AUTH")
- authenticate(username: string, password: string) {
- console.log(`Authenticating user ${username}`);
- return { success: true, token: "fake-jwt-token" };
- }
- }
- const apiService = new ApiService();
- apiService.fetchData("/users");
- apiService.authenticate("admin", "password");
复制代码
装饰器组合
TypeScript允许多个装饰器应用于同一个声明,它们会按照从下到上的顺序组合应用。
- function first() {
- console.log("first(): factory evaluated");
- return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("first(): called");
- };
- }
- function second() {
- console.log("second(): factory evaluated");
- return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("second(): called");
- };
- }
- function third() {
- console.log("third(): factory evaluated");
- return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("third(): called");
- };
- }
- class ExampleClass {
- @first()
- @second()
- @third()
- method() {}
- }
- // 输出顺序:
- // first(): factory evaluated
- // second(): factory evaluated
- // third(): factory evaluated
- // third(): called
- // second(): called
- // first(): called
复制代码
装饰器元数据
TypeScript支持通过reflect-metadata库为装饰器添加元数据,这对于实现依赖注入等功能特别有用。
首先,安装reflect-metadata:
- npm install reflect-metadata
复制代码
然后,在应用入口导入它:
- import "reflect-metadata";
复制代码
使用元数据的示例:
- import "reflect-metadata";
- // 定义元数据键
- const DESIGN_PARAM_TYPES = "design:paramtypes";
- const DESIGN_RETURN_TYPE = "design:returntype";
- const DESIGN_TYPE = "design:type";
- // 类型装饰器
- function typeInfo(target: any, propertyKey: string) {
- const types = Reflect.getMetadata(DESIGN_TYPE, target, propertyKey);
- console.log(`Type of ${propertyKey}: ${types?.name}`);
-
- const paramTypes = Reflect.getMetadata(DESIGN_PARAM_TYPES, target, propertyKey);
- if (paramTypes) {
- console.log(`Parameter types for ${propertyKey}:`, paramTypes.map(t => t.name));
- }
-
- const returnType = Reflect.getMetadata(DESIGN_RETURN_TYPE, target, propertyKey);
- if (returnType) {
- console.log(`Return type for ${propertyKey}: ${returnType.name}`);
- }
- }
- class UserService {
- @typeInfo
- name: string;
-
- @typeInfo
- getUser(id: number): string {
- return `User ${id}`;
- }
-
- @typeInfo
- createUser(name: string, email: string): { id: number; name: string; email: string } {
- return { id: Date.now(), name, email };
- }
- }
- // 输出:
- // Type of name: String
- // Type of getUser: Function
- // Parameter types for getUser: [ 'Number' ]
- // Return type for getUser: String
- // Type of createUser: Function
- // Parameter types for createUser: [ 'String', 'String' ]
- // Return type for createUser: Object
复制代码
装饰器与反射结合
结合反射API,我们可以创建更强大的装饰器,例如自动序列化和反序列化对象。
- import "reflect-metadata";
- // 序列化元数据键
- const SERIALIZE_KEY = "custom:serialize";
- // 序列化装饰器
- function serialize() {
- return function(target: any, propertyKey: string) {
- Reflect.defineMetadata(SERIALIZE_KEY, true, target, propertyKey);
- };
- }
- // 序列化工具
- class SerializationHelper {
- static serialize(obj: any): any {
- const result: any = {};
-
- // 获取对象的所有属性
- for (const key in obj) {
- if (obj.hasOwnProperty(key)) {
- // 检查属性是否有序列化元数据
- const shouldSerialize = Reflect.getMetadata(SERIALIZE_KEY, obj, key);
-
- if (shouldSerialize) {
- result[key] = obj[key];
- }
- }
- }
-
- return result;
- }
-
- static deserialize<T>(constructor: new (...args: any[]) => T, data: any): T {
- const instance = new constructor();
-
- // 获取构造函数的所有属性
- const properties = Object.getOwnPropertyNames(constructor.prototype);
-
- for (const key of properties) {
- if (key !== "constructor" && data[key] !== undefined) {
- // 检查属性是否有序列化元数据
- const shouldSerialize = Reflect.getMetadata(SERIALIZE_KEY, constructor.prototype, key);
-
- if (shouldSerialize) {
- instance[key] = data[key];
- }
- }
- }
-
- return instance;
- }
- }
- class User {
- @serialize()
- id: number;
-
- @serialize()
- name: string;
-
- @serialize()
- email: string;
-
- // 这个属性不会被序列化
- password: string;
-
- constructor(id?: number, name?: string, email?: string, password?: string) {
- this.id = id ?? 0;
- this.name = name ?? "";
- this.email = email ?? "";
- this.password = password ?? "";
- }
- }
- // 创建用户
- const user = new User(1, "John Doe", "john@example.com", "secret");
- // 序列化用户
- const serialized = SerializationHelper.serialize(user);
- console.log("Serialized user:", serialized);
- // 输出: Serialized user: { id: 1, name: 'John Doe', email: 'john@example.com' }
- // 反序列化用户
- const deserialized = SerializationHelper.deserialize(User, { id: 2, name: "Jane Smith", email: "jane@example.com" });
- console.log("Deserialized user:", deserialized);
- // 输出: Deserialized user: User { id: 2, name: 'Jane Smith', email: 'jane@example.com', password: '' }
复制代码
装饰器错误处理
在装饰器中正确处理错误非常重要,特别是在生产环境中。
- function safeDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- try {
- // 记录方法调用
- console.log(`[SAFE] Calling ${propertyKey} with args:`, args);
-
- // 执行原始方法
- const result = originalMethod.apply(this, args);
-
- // 处理Promise
- if (result && typeof result.then === 'function') {
- return result
- .then((res: any) => {
- console.log(`[SAFE] ${propertyKey} resolved successfully`);
- return res;
- })
- .catch((err: any) => {
- console.error(`[SAFE] ${propertyKey} rejected with error:`, err);
- // 可以在这里添加错误恢复逻辑或重新抛出错误
- throw err;
- });
- }
-
- console.log(`[SAFE] ${propertyKey} executed successfully`);
- return result;
- } catch (error) {
- console.error(`[SAFE] Error in ${propertyKey}:`, error);
- // 可以在这里添加错误恢复逻辑或重新抛出错误
- throw error;
- }
- };
-
- return descriptor;
- }
- class ServiceWithErrors {
- @safeDecorator
- synchronousMethod() {
- console.log("Executing synchronous method");
- if (Math.random() > 0.5) {
- throw new Error("Random synchronous error");
- }
- return "Sync result";
- }
-
- @safeDecorator
- asynchronousMethod() {
- console.log("Executing asynchronous method");
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- if (Math.random() > 0.5) {
- reject(new Error("Random asynchronous error"));
- } else {
- resolve("Async result");
- }
- }, 100);
- });
- }
- }
- const service = new ServiceWithErrors();
- // 测试同步方法
- for (let i = 0; i < 3; i++) {
- try {
- console.log(`Sync call ${i + 1}:`, service.synchronousMethod());
- } catch (error) {
- console.log(`Sync call ${i + 1} failed:`, error.message);
- }
- }
- // 测试异步方法
- for (let i = 0; i < 3; i++) {
- service.asynchronousMethod()
- .then(result => console.log(`Async call ${i + 1}:`, result))
- .catch(error => console.log(`Async call ${i + 1} failed:`, error.message));
- }
复制代码
装饰器与框架集成
许多流行的TypeScript框架和库都广泛使用装饰器,下面我们来看看如何在几个主流框架中使用装饰器。
Angular中的装饰器
Angular是一个广泛使用装饰器的前端框架,几乎所有核心功能都通过装饰器实现。
- import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
- // 组件装饰器
- @Component({
- selector: 'app-user-profile',
- templateUrl: './user-profile.component.html',
- styleUrls: ['./user-profile.component.css']
- })
- export class UserProfileComponent implements OnInit {
- // 输入属性装饰器
- @Input() user: any;
-
- // 输出事件装饰器
- @Output() updateUser = new EventEmitter<any>();
-
- constructor() { }
-
- // 生命周期钩子
- ngOnInit(): void {
- console.log('UserProfileComponent initialized');
- }
-
- // 方法装饰器(自定义)
- @log
- saveUser() {
- this.updateUser.emit(this.user);
- }
- }
- // 自定义日志装饰器
- function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`[${target.constructor.name}] Calling ${propertyKey}`);
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- }
复制代码
NestJS中的装饰器
NestJS是一个用于构建高效、可扩展的Node.js服务器端应用程序的框架,它大量使用装饰器。
- import { Controller, Get, Post, Body, Param, UseGuards, SetMetadata } from '@nestjs/common';
- import { UserService } from './user.service';
- import { CreateUserDto } from './dto/create-user.dto';
- import { JwtAuthGuard } from '../auth/jwt-auth.guard';
- import { RolesGuard } from '../auth/roles.guard';
- import { Roles } from '../auth/roles.decorator';
- // 控制器装饰器
- @Controller('users')
- @UseGuards(JwtAuthGuard, RolesGuard)
- export class UsersController {
- constructor(private readonly userService: UserService) {}
- // 路由装饰器
- @Get()
- @Roles('admin')
- findAll() {
- return this.userService.findAll();
- }
- @Get(':id')
- @Roles('admin', 'user')
- findOne(@Param('id') id: string) {
- return this.userService.findOne(id);
- }
- @Post()
- @Roles('admin')
- create(@Body() createUserDto: CreateUserDto) {
- return this.userService.create(createUserDto);
- }
- }
- // 自定义角色装饰器
- export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
- // 角色守卫
- import { CanActivate, ExecutionContext } from '@nestjs/common';
- import { Reflector } from '@nestjs/core';
- export class RolesGuard implements CanActivate {
- constructor(private reflector: Reflector) {}
- canActivate(context: ExecutionContext): boolean {
- const roles = this.reflector.get<string[]>('roles', context.getHandler());
- if (!roles) {
- return true;
- }
-
- const request = context.switchToHttp().getRequest();
- const user = request.user;
-
- return roles.some(role => user.roles?.includes(role));
- }
- }
复制代码
TypeORM中的装饰器
TypeORM是一个ORM框架,它使用装饰器来定义实体和关系。
- import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
- import { Post } from './Post';
- import { Profile } from './Profile';
- // 实体装饰器
- @Entity()
- export class User {
- // 主键装饰器
- @PrimaryGeneratedColumn()
- id: number;
- // 列装饰器
- @Column()
- firstName: string;
- @Column()
- lastName: string;
- @Column({ unique: true })
- email: string;
- @Column()
- @Column({ select: false }) // 不默认查询此列
- password: string;
- @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
- createdAt: Date;
- // 一对多关系装饰器
- @OneToMany(() => Post, post => post.author)
- posts: Post[];
- // 一对一关系装饰器
- @ManyToOne(() => Profile, profile => profile.user)
- @JoinColumn()
- profile: Profile;
- }
- @Entity()
- export class Post {
- @PrimaryGeneratedColumn()
- id: number;
- @Column()
- title: string;
- @Column('text')
- content: string;
- // 多对一关系装饰器
- @ManyToOne(() => User, user => user.posts)
- author: User;
- }
复制代码
class-validator中的装饰器
class-validator是一个用于对象验证的库,它使用装饰器来定义验证规则。
- import { validate, IsEmail, IsNotEmpty, Length, IsOptional, IsNumberString } from 'class-validator';
- export class CreateUserDto {
- @IsNotEmpty()
- @Length(3, 20)
- username: string;
- @IsEmail()
- email: string;
- @IsNotEmpty()
- @Length(8, 100)
- password: string;
- @IsOptional()
- @Length(0, 100)
- bio?: string;
- @IsOptional()
- @IsNumberString()
- age?: string;
- }
- // 使用验证
- async function createUser(userData: CreateUserDto) {
- const user = new CreateUserDto();
- user.username = userData.username;
- user.email = userData.email;
- user.password = userData.password;
- user.bio = userData.bio;
- user.age = userData.age;
- const errors = await validate(user);
- if (errors.length > 0) {
- console.log('Validation failed. Errors:', errors);
- throw new Error('Validation failed');
- } else {
- console.log('Validation succeeded');
- // 保存用户到数据库
- return { success: true, user };
- }
- }
- // 测试验证
- createUser({
- username: 'john_doe',
- email: 'john@example.com',
- password: 'password123'
- }).then(console.log).catch(console.error);
- createUser({
- username: 'jd', // 太短
- email: 'invalid-email', // 无效邮箱
- password: '123', // 太短
- age: 'not-a-number' // 不是数字字符串
- }).then(console.log).catch(console.error);
复制代码
提升代码质量与开发效率的具体案例
让我们通过几个具体的案例,看看装饰器如何在实际项目中提升代码质量和开发效率。
案例1:API请求处理
在处理API请求时,我们通常需要处理错误、验证数据、转换响应等。使用装饰器可以将这些横切关注点与业务逻辑分离。
- import "reflect-metadata";
- // 定义错误类型
- class ApiError extends Error {
- constructor(
- public statusCode: number,
- message: string
- ) {
- super(message);
- this.name = "ApiError";
- }
- }
- // 路由处理装饰器
- function routeHandler() {
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(req: any, res: any, next: any) {
- try {
- // 执行原始方法
- const result = await originalMethod.apply(this, [req, res, next]);
-
- // 如果方法返回结果,则发送JSON响应
- if (result !== undefined) {
- res.json({
- success: true,
- data: result
- });
- }
- } catch (error) {
- // 处理已知错误
- if (error instanceof ApiError) {
- res.status(error.statusCode).json({
- success: false,
- message: error.message
- });
- return;
- }
-
- // 处理未知错误
- console.error("Unhandled error:", error);
- res.status(500).json({
- success: false,
- message: "Internal server error"
- });
- }
- };
-
- return descriptor;
- };
- }
- // 验证请求体装饰器
- function validateBody(dtoClass: any) {
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(req: any, res: any, next: any) {
- try {
- // 创建DTO实例
- const dto = Object.assign(new dtoClass(), req.body);
-
- // 验证DTO
- const errors = await validate(dto);
- if (errors.length > 0) {
- throw new ApiError(400, "Invalid request body");
- }
-
- // 将验证后的DTO附加到请求对象
- req.validatedBody = dto;
-
- // 调用原始方法
- return originalMethod.apply(this, [req, res, next]);
- } catch (error) {
- next(error);
- }
- };
-
- return descriptor;
- };
- }
- // 示例DTO
- import { validate, IsEmail, IsNotEmpty, Length } from 'class-validator';
- class CreateUserDto {
- @IsNotEmpty()
- @Length(3, 20)
- username: string;
- @IsEmail()
- email: string;
- @IsNotEmpty()
- @Length(8, 100)
- password: string;
- }
- // 用户控制器
- class UserController {
- @routeHandler()
- @validateBody(CreateUserDto)
- async createUser(req: any, res: any) {
- const { username, email, password } = req.validatedBody;
-
- // 检查用户是否已存在
- const existingUser = await findUserByEmail(email);
- if (existingUser) {
- throw new ApiError(409, "User with this email already exists");
- }
-
- // 创建用户
- const user = await saveUser({ username, email, password });
-
- // 返回用户数据(不包含密码)
- const { password: _, ...userWithoutPassword } = user;
- return userWithoutPassword;
- }
-
- @routeHandler()
- async getUser(req: any, res: any) {
- const userId = parseInt(req.params.id);
-
- if (isNaN(userId)) {
- throw new ApiError(400, "Invalid user ID");
- }
-
- const user = await findUserById(userId);
- if (!user) {
- throw new ApiError(404, "User not found");
- }
-
- const { password: _, ...userWithoutPassword } = user;
- return userWithoutPassword;
- }
- }
- // 模拟数据库函数
- async function findUserByEmail(email: string) {
- // 模拟数据库查询
- return null;
- }
- async function findUserById(id: number) {
- // 模拟数据库查询
- if (id === 1) {
- return {
- id: 1,
- username: "john_doe",
- email: "john@example.com",
- password: "hashed_password"
- };
- }
- return null;
- }
- async function saveUser(userData: any) {
- // 模拟保存用户
- return {
- id: Math.floor(Math.random() * 1000),
- ...userData
- };
- }
- // 模拟Express请求处理
- async function simulateRequest(controller: any, method: string, req: any) {
- const res = {
- json: (data: any) => console.log("Response:", JSON.stringify(data, null, 2)),
- status: (code: number) => {
- console.log(`Status: ${code}`);
- return res;
- }
- };
-
- const next = (error: any) => {
- console.log("Error:", error.message);
- };
-
- try {
- await controller[method](req, res, next);
- } catch (error) {
- next(error);
- }
- }
- // 测试用例
- const userController = new UserController();
- // 测试创建用户 - 成功
- console.log("\n=== Test 1: Create user (success) ===");
- simulateRequest(userController, "createUser", {
- body: {
- username: "john_doe",
- email: "john@example.com",
- password: "password123"
- }
- });
- // 测试创建用户 - 验证失败
- console.log("\n=== Test 2: Create user (validation failure) ===");
- simulateRequest(userController, "createUser", {
- body: {
- username: "jd", // 太短
- email: "invalid-email", // 无效邮箱
- password: "123" // 太短
- }
- });
- // 测试获取用户 - 成功
- console.log("\n=== Test 3: Get user (success) ===");
- simulateRequest(userController, "getUser", {
- params: { id: "1" }
- });
- // 测试获取用户 - 无效ID
- console.log("\n=== Test 4: Get user (invalid ID) ===");
- simulateRequest(userController, "getUser", {
- params: { id: "invalid" }
- });
- // 测试获取用户 - 不存在
- console.log("\n=== Test 5: Get user (not found) ===");
- simulateRequest(userController, "getUser", {
- params: { id: "999" }
- });
复制代码
案例2:数据库事务管理
在处理数据库操作时,事务管理是一个重要的横切关注点。使用装饰器可以优雅地实现事务管理。
- import "reflect-metadata";
- // 事务元数据键
- const TRANSACTION_KEY = "custom:transaction";
- // 事务装饰器
- function transactional() {
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(...args: any[]) {
- // 开始事务
- const transaction = await this.beginTransaction();
- console.log(`[TRANSACTION] Started for ${propertyKey}`);
-
- try {
- // 将事务附加到当前实例,以便其他方法使用
- Reflect.defineMetadata(TRANSACTION_KEY, transaction, this);
-
- // 执行原始方法
- const result = await originalMethod.apply(this, args);
-
- // 提交事务
- await this.commitTransaction(transaction);
- console.log(`[TRANSACTION] Committed for ${propertyKey}`);
-
- return result;
- } catch (error) {
- // 回滚事务
- await this.rollbackTransaction(transaction);
- console.log(`[TRANSACTION] Rolled back for ${propertyKey}`);
-
- // 重新抛出错误
- throw error;
- } finally {
- // 清除事务元数据
- Reflect.deleteMetadata(TRANSACTION_KEY, this);
- }
- };
-
- return descriptor;
- };
- }
- // 获取当前事务的辅助函数
- function getCurrentTransaction(target: any) {
- return Reflect.getMetadata(TRANSACTION_KEY, target);
- }
- // 模拟数据库服务
- class DatabaseService {
- private transactions: Map<string, any> = new Map();
-
- async beginTransaction() {
- const id = `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
- const transaction = { id, committed: false, rolledBack: false };
- this.transactions.set(id, transaction);
- console.log(`[DB] Begin transaction: ${id}`);
- return transaction;
- }
-
- async commitTransaction(transaction: any) {
- if (transaction.rolledBack) {
- throw new Error("Cannot commit a rolled back transaction");
- }
-
- transaction.committed = true;
- console.log(`[DB] Commit transaction: ${transaction.id}`);
- }
-
- async rollbackTransaction(transaction: any) {
- if (transaction.committed) {
- throw new Error("Cannot rollback a committed transaction");
- }
-
- transaction.rolledBack = true;
- console.log(`[DB] Rollback transaction: ${transaction.id}`);
- }
-
- async query(sql: string, params: any[] = [], transaction?: any) {
- const txId = transaction ? ` (tx: ${transaction.id})` : "";
- console.log(`[DB] Executing query${txId}: ${sql}`, params);
-
- // 模拟查询延迟
- await new Promise(resolve => setTimeout(resolve, 100));
-
- // 模拟查询结果
- if (sql.startsWith("INSERT")) {
- return { insertId: Math.floor(Math.random() * 1000) };
- } else if (sql.startsWith("SELECT")) {
- return [
- { id: 1, name: "John Doe", email: "john@example.com" },
- { id: 2, name: "Jane Smith", email: "jane@example.com" }
- ];
- }
-
- return { affectedRows: 1 };
- }
- }
- // 用户服务
- class UserService {
- private db: DatabaseService;
-
- constructor(db: DatabaseService) {
- this.db = db;
- }
-
- async beginTransaction() {
- return this.db.beginTransaction();
- }
-
- async commitTransaction(transaction: any) {
- return this.db.commitTransaction(transaction);
- }
-
- async rollbackTransaction(transaction: any) {
- return this.db.rollbackTransaction(transaction);
- }
-
- @transactional()
- async createUserWithProfile(userData: any, profileData: any) {
- // 获取当前事务
- const transaction = getCurrentTransaction(this);
-
- // 创建用户
- const userResult = await this.db.query(
- "INSERT INTO users (name, email) VALUES (?, ?)",
- [userData.name, userData.email],
- transaction
- );
-
- const userId = userResult.insertId;
-
- // 创建用户档案
- await this.db.query(
- "INSERT INTO profiles (user_id, bio, avatar) VALUES (?, ?, ?)",
- [userId, profileData.bio, profileData.avatar],
- transaction
- );
-
- // 返回创建的用户和档案
- return {
- user: { id: userId, ...userData },
- profile: { userId, ...profileData }
- };
- }
-
- @transactional()
- async transferFunds(fromUserId: number, toUserId: number, amount: number) {
- // 获取当前事务
- const transaction = getCurrentTransaction(this);
-
- // 检查发送方账户余额
- const balanceResult = await this.db.query(
- "SELECT balance FROM accounts WHERE user_id = ?",
- [fromUserId],
- transaction
- );
-
- if (balanceResult.length === 0) {
- throw new Error("Sender account not found");
- }
-
- const balance = balanceResult[0].balance;
-
- if (balance < amount) {
- throw new Error("Insufficient funds");
- }
-
- // 从发送方账户扣除金额
- await this.db.query(
- "UPDATE accounts SET balance = balance - ? WHERE user_id = ?",
- [amount, fromUserId],
- transaction
- );
-
- // 向接收方账户添加金额
- await this.db.query(
- "UPDATE accounts SET balance = balance + ? WHERE user_id = ?",
- [amount, toUserId],
- transaction
- );
-
- // 记录交易
- await this.db.query(
- "INSERT INTO transactions (from_user_id, to_user_id, amount) VALUES (?, ?, ?)",
- [fromUserId, toUserId, amount],
- transaction
- );
-
- return { success: true, message: "Transfer completed" };
- }
- }
- // 测试
- const db = new DatabaseService();
- const userService = new UserService(db);
- // 测试创建用户和档案
- console.log("\n=== Test 1: Create user with profile ===");
- userService.createUserWithProfile(
- { name: "John Doe", email: "john@example.com" },
- { bio: "Software developer", avatar: "avatar.jpg" }
- ).then(result => {
- console.log("Result:", result);
- }).catch(error => {
- console.error("Error:", error.message);
- });
- // 测试资金转账 - 成功
- console.log("\n=== Test 2: Transfer funds (success) ===");
- userService.transferFunds(1, 2, 100)
- .then(result => {
- console.log("Result:", result);
- })
- .catch(error => {
- console.error("Error:", error.message);
- });
- // 测试资金转账 - 余额不足(应该回滚)
- console.log("\n=== Test 3: Transfer funds (insufficient funds) ===");
- userService.transferFunds(1, 2, 10000)
- .then(result => {
- console.log("Result:", result);
- })
- .catch(error => {
- console.error("Error:", error.message);
- });
复制代码
案例3:性能监控与优化
装饰器可以用于监控应用性能,帮助识别和解决性能瓶颈。
- // 性能监控装饰器
- function performanceMonitor(thresholdMs: number = 100) {
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(...args: any[]) {
- const startTime = performance.now();
- const memoryBefore = process.memoryUsage();
-
- try {
- // 执行原始方法
- const result = await originalMethod.apply(this, args);
-
- const endTime = performance.now();
- const memoryAfter = process.memoryUsage();
- const duration = endTime - startTime;
-
- // 计算内存使用差异
- const memoryDiff = {
- rss: memoryAfter.rss - memoryBefore.rss,
- heapTotal: memoryAfter.heapTotal - memoryBefore.heapTotal,
- heapUsed: memoryAfter.heapUsed - memoryBefore.heapUsed,
- external: memoryAfter.external - memoryBefore.external
- };
-
- // 记录性能数据
- console.log(`[PERFORMANCE] ${propertyKey} completed in ${duration.toFixed(2)}ms`);
- console.log(`[MEMORY] Usage difference:`, {
- rss: `${(memoryDiff.rss / 1024 / 1024).toFixed(2)} MB`,
- heapTotal: `${(memoryDiff.heapTotal / 1024 / 1024).toFixed(2)} MB`,
- heapUsed: `${(memoryDiff.heapUsed / 1024 / 1024).toFixed(2)} MB`,
- external: `${(memoryDiff.external / 1024 / 1024).toFixed(2)} MB`
- });
-
- // 如果执行时间超过阈值,记录警告
- if (duration > thresholdMs) {
- console.warn(`[PERFORMANCE WARNING] ${propertyKey} exceeded threshold of ${thresholdMs}ms`);
-
- // 这里可以添加额外的性能分析逻辑
- // 例如:记录调用堆栈、分析参数等
- }
-
- return result;
- } catch (error) {
- const endTime = performance.now();
- const duration = endTime - startTime;
-
- console.error(`[PERFORMANCE] ${propertyKey} failed after ${duration.toFixed(2)}ms`);
- console.error(`[ERROR]`, error);
-
- throw error;
- }
- };
-
- return descriptor;
- };
- }
- // 缓存装饰器
- function cache(ttlMs: number = 5000) {
- const cacheStore = new Map<string, { value: any; expiry: number }>();
-
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(...args: any[]) {
- // 生成缓存键
- const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
-
- // 检查缓存
- const cachedItem = cacheStore.get(cacheKey);
- if (cachedItem && Date.now() < cachedItem.expiry) {
- console.log(`[CACHE] Hit for ${propertyKey}`);
- return cachedItem.value;
- }
-
- // 缓存未命中,执行方法
- console.log(`[CACHE] Miss for ${propertyKey}`);
- const result = await originalMethod.apply(this, args);
-
- // 存入缓存
- cacheStore.set(cacheKey, {
- value: result,
- expiry: Date.now() + ttlMs
- });
-
- return result;
- };
-
- return descriptor;
- };
- }
- // 数据服务
- class DataService {
- @performanceMonitor(50)
- @cache(10000) // 缓存10秒
- async fetchData(id: number) {
- console.log(`[SERVICE] Fetching data for ID: ${id}`);
-
- // 模拟网络请求
- await new Promise(resolve => setTimeout(resolve, 30 + Math.random() * 50));
-
- // 模拟数据处理
- let result = 0;
- for (let i = 0; i < 100000; i++) {
- result += Math.sqrt(i);
- }
-
- return {
- id,
- data: `Sample data for ID ${id}`,
- calculatedValue: result
- };
- }
-
- @performanceMonitor(200)
- async processData(data: any[]) {
- console.log(`[SERVICE] Processing ${data.length} items`);
-
- // 模拟复杂处理
- await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 150));
-
- // 模拟CPU密集型操作
- return data.map(item => {
- let processed = { ...item };
-
- // 模拟一些计算
- for (let i = 0; i < 10000; i++) {
- processed.value = Math.sqrt(item.id * i);
- }
-
- processed.processedAt = new Date().toISOString();
- return processed;
- });
- }
- }
- // 测试
- const dataService = new DataService();
- // 测试数据获取(第一次会从服务获取,第二次会从缓存获取)
- console.log("\n=== Test 1: Fetch data (first call) ===");
- dataService.fetchData(123)
- .then(result => {
- console.log("Result:", result.id);
- })
- .catch(console.error);
- console.log("\n=== Test 2: Fetch data (second call - should be cached) ===");
- dataService.fetchData(123)
- .then(result => {
- console.log("Result:", result.id);
- })
- .catch(console.error);
- // 测试数据处理
- console.log("\n=== Test 3: Process data ===");
- const testData = Array.from({ length: 100 }, (_, i) => ({
- id: i + 1,
- name: `Item ${i + 1}`
- }));
- dataService.processData(testData)
- .then(result => {
- console.log(`Processed ${result.length} items`);
- })
- .catch(console.error);
复制代码
常见问题与解决方案
在使用TypeScript装饰器的过程中,开发者可能会遇到一些常见问题。下面我们探讨这些问题及其解决方案。
问题1:装饰器执行顺序
问题:多个装饰器应用于同一声明时,执行顺序可能不符合预期。
解决方案:理解装饰器的执行顺序。装饰器工厂函数从上到下执行,而装饰器函数从下到上执行。
- function decoratorA() {
- console.log("decoratorA factory");
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("decoratorA called");
- };
- }
- function decoratorB() {
- console.log("decoratorB factory");
- return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("decoratorB called");
- };
- }
- class Example {
- @decoratorA()
- @decoratorB()
- method() {}
- }
- // 输出:
- // decoratorA factory
- // decoratorB factory
- // decoratorB called
- // decoratorA called
复制代码
问题2:装饰器与属性描述符
问题:属性装饰器不能修改属性描述符,导致无法直接添加getter/setter。
解决方案:使用Object.defineProperty在属性装饰器中重新定义属性。
- function logChanges(target: any, propertyKey: string) {
- let value: any;
-
- const getter = function() {
- console.log(`Getting value for ${propertyKey}`);
- return value;
- };
-
- const setter = function(newVal: any) {
- console.log(`Setting value for ${propertyKey} to ${newVal}`);
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- }
- class MyClass {
- @logChanges
- myProperty: string;
- }
- const instance = new MyClass();
- instance.myProperty = "test";
- console.log(instance.myProperty);
复制代码
问题3:装饰器与继承
问题:装饰器在继承层次结构中的行为可能不符合预期,特别是当子类覆盖了父类的装饰方法时。
解决方案:了解装饰器如何影响继承链,并在必要时手动处理继承情况。
- function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`[${target.constructor.name}] Calling ${propertyKey}`);
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- }
- class Parent {
- @log
- method() {
- console.log("Parent method implementation");
- }
- }
- class Child extends Parent {
- @log
- method() {
- super.method();
- console.log("Child method implementation");
- }
- }
- const child = new Child();
- child.method();
- // 输出:
- // [Child] Calling method
- // [Parent] Calling method
- // Parent method implementation
- // Child method implementation
复制代码
问题4:装饰器与私有属性
问题:装饰器无法直接访问类的私有属性或方法。
解决方案:使用TypeScript的元数据或反射API,或者将需要访问的成员改为protected/public。
- function accessPrivate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- // 无法直接访问私有属性
- // console.log(this.privateProperty); // 错误
-
- // 使用反射API访问
- const privateValue = (this as any)["privateProperty"];
- console.log(`Accessed private property: ${privateValue}`);
-
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- }
- class MyClass {
- private privateProperty = "secret";
-
- @accessPrivate
- method() {
- console.log("Method called");
- }
- }
- const instance = new MyClass();
- instance.method();
复制代码
问题5:装饰器与异步方法
问题:装饰器在处理异步方法时可能无法正确处理Promise。
解决方案:在装饰器中检查返回值是否为Promise,并相应地处理。
- function asyncLog(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(...args: any[]) {
- console.log(`[${propertyKey}] Started`);
-
- try {
- const result = originalMethod.apply(this, args);
-
- // 检查是否返回Promise
- if (result && typeof result.then === 'function') {
- const awaitedResult = await result;
- console.log(`[${propertyKey}] Completed successfully`);
- return awaitedResult;
- }
-
- console.log(`[${propertyKey}] Completed successfully`);
- return result;
- } catch (error) {
- console.error(`[${propertyKey}] Failed:`, error);
- throw error;
- }
- };
-
- return descriptor;
- }
- class AsyncService {
- @asyncLog
- async asyncMethod() {
- await new Promise(resolve => setTimeout(resolve, 100));
- return "Async result";
- }
-
- @asyncLog
- syncMethod() {
- return "Sync result";
- }
- }
- const service = new AsyncService();
- service.asyncMethod().then(console.log);
- console.log(service.syncMethod());
复制代码
问题6:装饰器与严格模式
问题:在TypeScript严格模式下,装饰器可能会遇到类型检查问题。
解决方案:使用适当的类型注解和类型断言,确保装饰器函数的类型安全。
- // 使用适当的类型注解
- function typedDecorator(
- target: Object,
- propertyKey: string | symbol,
- descriptor: TypedPropertyDescriptor<(...args: any[]) => any>
- ) {
- const originalMethod = descriptor.value!;
-
- descriptor.value = function(this: any, ...args: any[]) {
- console.log(`[${String(propertyKey)}] Called with args:`, args);
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- }
- class StrictClass {
- @typedDecorator
- method(arg1: string, arg2: number): boolean {
- console.log(`Method called with ${arg1} and ${arg2}`);
- return true;
- }
- }
- const instance = new StrictClass();
- instance.method("test", 123);
复制代码
总结与展望
TypeScript装饰器是一种强大的元编程特性,它允许开发者以声明式的方式增强类、方法、属性和参数的功能。通过本文的深入探讨,我们了解了装饰器的基本概念、类型、实际应用场景以及实战技巧,并看到了装饰器如何在实际项目中提升代码质量和开发效率。
装饰器的优势
1. 关注点分离:装饰器可以将横切关注点(如日志、验证、缓存等)与业务逻辑分离,使代码更加清晰和可维护。
2. 代码复用:装饰器可以在多个地方重用,减少重复代码,提高开发效率。
3. 声明式编程:装饰器提供了一种声明式的方式来添加功能,使代码更加简洁和易读。
4. 元编程能力:装饰器提供了一种在运行时修改或扩展代码行为的能力,增加了代码的灵活性。
关注点分离:装饰器可以将横切关注点(如日志、验证、缓存等)与业务逻辑分离,使代码更加清晰和可维护。
代码复用:装饰器可以在多个地方重用,减少重复代码,提高开发效率。
声明式编程:装饰器提供了一种声明式的方式来添加功能,使代码更加简洁和易读。
元编程能力:装饰器提供了一种在运行时修改或扩展代码行为的能力,增加了代码的灵活性。
装饰器的局限性
1. 实验性特性:装饰器在TypeScript中仍然是一个实验性特性,其API和实现可能会在未来的版本中发生变化。
2. 性能开销:装饰器可能会引入一些性能开销,特别是在频繁调用的方法上。
3. 调试复杂性:由于装饰器在运行时修改代码行为,可能会增加调试的复杂性。
4. 学习曲线:对于初学者来说,装饰器的概念和工作原理可能需要一些时间来理解。
实验性特性:装饰器在TypeScript中仍然是一个实验性特性,其API和实现可能会在未来的版本中发生变化。
性能开销:装饰器可能会引入一些性能开销,特别是在频繁调用的方法上。
调试复杂性:由于装饰器在运行时修改代码行为,可能会增加调试的复杂性。
学习曲线:对于初学者来说,装饰器的概念和工作原理可能需要一些时间来理解。
未来展望
随着ECMAScript装饰器提案的推进,TypeScript装饰器的实现可能会进一步标准化和完善。我们可以期待:
1. 更稳定的API:随着装饰器提案的成熟,TypeScript可能会提供更稳定和一致的装饰器API。
2. 更好的工具支持:IDE和其他开发工具可能会提供更好的装饰器支持,包括代码提示、重构和调试功能。
3. 更广泛的应用:随着装饰器的普及,我们可能会看到更多的框架和库采用装饰器作为其核心特性。
4. 性能优化:未来的TypeScript版本可能会优化装饰器的实现,减少其性能开销。
更稳定的API:随着装饰器提案的成熟,TypeScript可能会提供更稳定和一致的装饰器API。
更好的工具支持:IDE和其他开发工具可能会提供更好的装饰器支持,包括代码提示、重构和调试功能。
更广泛的应用:随着装饰器的普及,我们可能会看到更多的框架和库采用装饰器作为其核心特性。
性能优化:未来的TypeScript版本可能会优化装饰器的实现,减少其性能开销。
最佳实践建议
1. 合理使用装饰器:不要过度使用装饰器,只在确实需要横切关注点时使用。
2. 保持装饰器简单:尽量保持装饰器函数简单和专注,避免在装饰器中实现复杂的逻辑。
3. 提供清晰的文档:为自定义装饰器提供清晰的文档,说明其用途、参数和行为。
4. 考虑性能影响:在性能敏感的代码中使用装饰器时,要考虑其性能影响,必要时进行性能测试。
5. 遵循命名约定:为装饰器函数和装饰器工厂使用一致的命名约定,提高代码可读性。
合理使用装饰器:不要过度使用装饰器,只在确实需要横切关注点时使用。
保持装饰器简单:尽量保持装饰器函数简单和专注,避免在装饰器中实现复杂的逻辑。
提供清晰的文档:为自定义装饰器提供清晰的文档,说明其用途、参数和行为。
考虑性能影响:在性能敏感的代码中使用装饰器时,要考虑其性能影响,必要时进行性能测试。
遵循命名约定:为装饰器函数和装饰器工厂使用一致的命名约定,提高代码可读性。
总之,TypeScript装饰器是一个强大的工具,可以帮助开发者编写更加清晰、可维护和高效的代码。通过合理地使用装饰器,我们可以显著提升代码质量和开发效率,使我们的应用程序更加健壮和易于维护。随着TypeScript和JavaScript生态系统的不断发展,装饰器无疑将在未来的前端和后端开发中扮演更加重要的角色。 |
|