Typescript中如何让方法装饰器标记的元数据可被外部访问
问题根因
你给PropertyDescriptor添加自定义属性的方案无法生效,是因为该描述符仅在装饰器执行、类方法定义阶段生效,当方法被挂载到类原型后,描述符本身不会和最终的方法函数对象产生关联,自然无法从方法上读取到你附加的属性。
可行实现方案
方案1:使用TS官方推荐的Reflect Metadata存储元数据(最稳妥,支持继承)
这是TS生态中处理装饰器元数据的标准方案,天然支持原型链继承,不会出现你之前静态字典在继承场景下的冲突问题。
- 前置配置
- 安装依赖:
npm i reflect-metadata - 开启
tsconfig.json配置:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }- 在项目入口处全局导入:
import 'reflect-metadata'
- 安装依赖:
- 装饰器实现
// 先定义运行环境枚举 enum RunEnv { Server = 'server', Client = 'client', Both = 'both' } // 用Symbol作为元数据key,避免命名冲突 const RUN_ENV_META_KEY = Symbol('run_env'); // 服务端方法装饰器 export function serverAction(target: any, propertyKey: string, descriptor: PropertyDescriptor) { Reflect.defineMetadata(RUN_ENV_META_KEY, RunEnv.Server, target, propertyKey); } // 客户端方法装饰器 export function clientAction(target: any, propertyKey: string, descriptor: PropertyDescriptor) { Reflect.defineMetadata(RUN_ENV_META_KEY, RunEnv.Client, target, propertyKey); } // 两端通用方法装饰器 export function universalAction(target: any, propertyKey: string, descriptor: PropertyDescriptor) { Reflect.defineMetadata(RUN_ENV_META_KEY, RunEnv.Both, target, propertyKey); } - 元数据读取方式
const testGame = new Game(); // 从实例读取方法的运行环境 const passMethodEnv = Reflect.getMetadata(RUN_ENV_META_KEY, Object.getPrototypeOf(testGame), 'pass'); // 也可以直接从类原型读取,不需要实例化 const getRandomNumberEnv = Reflect.getMetadata(RUN_ENV_META_KEY, Game.prototype, 'getRandomNumber');
方案2:无额外依赖,直接给方法本身附加标记
如果不想引入第三方依赖,可以直接把标记附加到被装饰的方法函数对象上:
export function clientAction(target: any, propertyKey: string, descriptor: PropertyDescriptor) { // descriptor.value就是被装饰的方法本身 (descriptor.value as any).runEnv = 'client'; }
读取方式非常直接:
const testGame = new Game(); console.log(testGame.pass.runEnv); // 输出 'client'
注意该方案的局限性:如果子类重写了被装饰的方法,新的方法不会自动继承该标记,需要重新装饰。
方案3:原型挂载标记集合(兼容继承无依赖)
如果需要同时支持继承、不需要重写自动继承标记,又不想引入依赖,可以在类原型上用Symbol存储标记集合:
const RUN_ENV_MAP = Symbol('run_env_map'); export function clientAction(target: any, propertyKey: string) { // 初始化时继承父类的标记集合,避免覆盖父类数据 if (!target[RUN_ENV_MAP]) { target[RUN_ENV_MAP] = Object.create(target[RUN_ENV_MAP] || null); } target[RUN_ENV_MAP][propertyKey] = 'client'; }
读取方式:
const testGame = new Game(); console.log(testGame[RUN_ENV_MAP]?.pass); // 输出 'client'
内容的提问来源于stack exchange,提问作者lochok
相关产品推荐
相关产品推荐

