为支持多类型的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
相关产品推荐
相关产品推荐

