如何从方法装饰器中访问类的元数据

9
我正在使用两个装饰器。一个是类装饰器,另一个是方法装饰器。 类装饰器定义了元数据,我想在方法装饰器中访问该元数据。
类装饰器:
function ClassDecorator(topic?: string): ClassDecorator {
    return (target) => {
        Reflect.defineMetadata('topic', topic, target);
        // I've also tried target.prototype instead of target
        return target;
    };
}

方法装饰器:
interface methodDecoratorOptions {
    cmd: string
}

function MethodDecorator(options: decoratorOptions) {
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        // HERE IS MY PROBLEM
        console.log('metaData is: ', Reflect.getMetadata('topic', target));
    }
}

这是我的Class定义:
@ClassDecorator('auth')
export class LoginClass {

    @MethodDecorator({
        cmd: 'login'
    })
    myMethod() {
        console.log('METHOD CALLED');
    }
}

问题:
MethodDecorator 中的以下行返回 metaData is: undefined 。为什么它是 undefined?
console.log('metaData is: ', Reflect.getMetadata('topic', target));

问题:
如何从 MethodDecorator 访问 ClassDecorator 定义的元数据?
1个回答

14

问题在于装饰器执行的顺序。方法修饰器先执行,然后是类修饰器的执行。这是有道理的,如果您考虑一下,类修饰器需要完整的类来执行操作,而创建类涉及创建方法并首先调用它们的修饰器。

一个简单的解决方法是让方法修饰器注册一个回调函数,该函数将由类修饰器在主题设置后调用:

function ClassDecorator(topic?: string): ClassDecorator {
    return (target) => {
        Reflect.defineMetadata('topic', topic, target.prototype);
        let topicFns: Array<() => void> = Reflect.getMetadata("topicCallbacks", target.prototype);
        if (topicFns) {
            topicFns.forEach(fn => fn());
        }
        return target;
    };
}

interface methodDecoratorOptions {
    cmd: string
}

function MethodDecorator(options: methodDecoratorOptions) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        let topicFns: Array<() => void> = Reflect.getMetadata("topicCallbacks", target);
        if (!topicFns) {
            Reflect.defineMetadata("topicCallbacks", topicFns = [], target);
        }
        topicFns.push(() => {
            console.log('metaData is: ', Reflect.getMetadata('topic', target));
        });
    }
}

@ClassDecorator('auth')
class LoginClass {

    @MethodDecorator({
        cmd: 'login'
    })
    myMethod() {
        console.log('METHOD CALLED');
    }
}

但是说实话,我不喜欢那种变通方法。它对我来说似乎不太易读。希望能找到更好的解决方案。 - tmuecksch
我不知道其他的解决方法,它确实使代码更难读。但是反过来说,装饰器是你基础架构的一部分,你只需要编写一次并且多次使用,而且装饰器的使用方式不会改变,这才是我认为更重要的部分。 - Titian Cernicova-Dragomir
你可以取消标记答案,等待更好的解决方法,我不介意。但是我曾经看到并使用过类似的解决方法来解决这个问题,但是还没有找到更好的解决方案,只有对此主题的变化(例如使用静态类字段而不是 Reflect API)。 - Titian Cernicova-Dragomir
我不是有意冒犯你。现在我明白了问题,对此非常感激。我不理解为什么开发人员选择那种装饰器执行顺序。也许他们有自己的理由,但对于我的情况来说,这是不方便的。 - tmuecksch
@tmuecksch 不会有任何冒犯,真的 :). 我相信一定有一个标准规定了这个。我也相信他们考虑过另一种方式,但是对于大多数用例来说,这种顺序更有意义,不幸的是对于你的情况不是这样。 - Titian Cernicova-Dragomir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接