You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为支持多类型的TC39装饰器编写正确的TypeScript类型

支持多类型的TC39装饰器编写正确的TypeScript类型

看起来你已经摸清楚了箭头函数字段的装饰器写法,但在扩展到类和方法装饰器时卡在了类型适配上——这很正常,因为不同类型的装饰器对应的上下文(context)和目标值(value)类型差异不小。下面我给你整理一个能完美适配类、方法、箭头函数字段的日志装饰器工厂,附带详细的类型说明:

首先,核心思路是通过context.kind的类型守卫来区分不同的装饰场景,让TypeScript自动推导对应场景下的value类型,避免泛型过度复杂。咱们直接上代码:

type Constructor<T = object> = new (...args: unknown[]) => T;

export function log(prefix = '[LOG]') {
  // 通用装饰器函数,自动适配类、方法、字段三种场景
  return function <T>(
    value: T,
    context: ClassDecoratorContext | ClassMethodDecoratorContext | ClassFieldDecoratorContext
  ) {
    // 处理类装饰器:kind为"class"时,value是类的构造函数
    if (context.kind === "class") {
      const OriginalClass = value as Constructor;
      return class extends OriginalClass {
        constructor(...args: unknown[]) {
          super(...args);
          console.log(`${prefix} 类 ${OriginalClass.name} 已实例化,参数: ${JSON.stringify(args)}`);
        }
      };
    }

    // 处理方法装饰器:kind为"method"时,value是类的原型方法
    if (context.kind === "method") {
      const originalMethod = value as (...args: unknown[]) => unknown;
      return function (this: unknown, ...args: unknown[]) {
        console.log(`${prefix} 方法 ${String(context.name)} 被调用,参数: ${JSON.stringify(args)}`);
        const result = originalMethod.call(this, ...args);
        
        // 兼容异步方法的日志输出
        if (result instanceof Promise) {
          return result.then(res => {
            console.log(`${prefix} 方法 ${String(context.name)} 异步调用完成,返回值: ${JSON.stringify(res)}`);
            return res;
          });
        }
        
        console.log(`${prefix} 方法 ${String(context.name)} 调用完成,返回值: ${JSON.stringify(result)}`);
        return result;
      };
    }

    // 处理字段装饰器(箭头函数字段):kind为"field"时,value是箭头函数本身
    if (context.kind === "field") {
      const originalArrowFn = value as (...args: unknown[]) => unknown;
      return function (this: unknown, ...args: unknown[]) {
        console.log(`${prefix} 箭头函数字段 ${String(context.name)} 被调用,参数: ${JSON.stringify(args)}`);
        const result = originalArrowFn.call(this, ...args);
        
        // 同样兼容异步箭头函数
        if (result instanceof Promise) {
          return result.then(res => {
            console.log(`${prefix} 箭头函数字段 ${String(context.name)} 异步调用完成,返回值: ${JSON.stringify(res)}`);
            return res;
          });
        }
        
        console.log(`${prefix} 箭头函数字段 ${String(context.name)} 调用完成,返回值: ${JSON.stringify(result)}`);
        return result;
      };
    }

    // 未覆盖的装饰场景(比如accessor),直接返回原value
    return value;
  };
}

关键细节说明:

  • 类型守卫的作用:当我们判断context.kind === "class"时,TypeScript会自动把context的类型缩小为ClassDecoratorContext,同时value的类型也会被推导为类构造函数,不用手动写复杂的条件泛型。
  • 泛型灵活性:用<T>作为通用泛型,让装饰器能适配任意类型的目标值,避免提前固定死参数和返回值类型。
  • 异步兼容:不管是方法还是箭头函数,都处理了Promise返回值的情况,确保异步调用的日志也能正确输出。
  • TS版本兼容:TypeScript 5.0就已经正式支持TC39标准装饰器,5.6只是做了类型优化,所以这个代码在5.6下完全可以正常运行,类型推导也不会有问题。

使用示例

你可以这样在代码里复用这个装饰器:

@log()
class UserService {
  @log('[USER-FN]')
  getUserInfo = (userId: string) => {
    return { id: userId, name: "Test User" };
  };

  @log('[USER-METHOD]')
  updateUserName(userId: string, newName: string) {
    return `用户 ${userId} 姓名已更新为 ${newName}`;
  }

  @log('[ASYNC-METHOD]')
  async fetchUserPosts(userId: string) {
    await new Promise(resolve => setTimeout(resolve, 200));
    return [{ id: "post1", title: "测试文章" }];
  }
}

const service = new UserService();
service.getUserInfo("123");
service.updateUserName("123", "New Name");
service.fetchUserPosts("123");

运行后你会看到控制台输出对应的日志,覆盖类实例化、方法调用、箭头函数调用的全场景。

备注:内容来源于stack exchange,提问作者errorline1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 14:58:16