Typescript 引用此上下文完整的修饰类方法

Typescript 引用此上下文完整的修饰类方法,typescript,decorator,Typescript,Decorator,我正在编写一个简短的decorator-helper函数,将类转换为事件侦听器 我的问题是,装饰程序会将装饰的方法注册为传入事件的回调,但装饰的方法不会保留它原来的此上下文 主要问题在这个场景中,我如何保留装饰方法的这个上下文 实施: export function EventHandler (topicKey: any): ClassDecorator { return function (target: any) { const subscriptions = Ref

我正在编写一个简短的decorator-helper函数,将类转换为事件侦听器

我的问题是,装饰程序会将装饰的方法注册为传入事件的回调,但装饰的方法不会保留它原来的
上下文

主要问题在这个场景中,我如何保留装饰方法的
这个
上下文

实施:

export function EventHandler (topicKey: any): ClassDecorator {
    return function (target: any) {
        const subscriptions = Reflect.getMetadata('subscriptions', target.prototype)

        const topic = Container.get<DomainTopicInterface>(topicKey)
        topic.subscribe(event => {
            if (subscriptions.length === 0) {
                throw new Error(`Event received for '${target.constructor.name}' but no handlers defined`)
            }
            subscriptions.forEach((subscription: any) => {
                subscription.callback(event) // <---- the this context is undefined
            })
        })

        return target
    }
}

export function Subscribe (targetClass: StaticDomainEvent<any>): MethodDecorator {
    return function (target: Function, methodName: string, descriptor: TypedPropertyDescriptor<any>) {
        let originalMethod = descriptor.value
        let subscriptions = Reflect.getMetadata('subscriptions', target)
        if (!subscriptions) { Reflect.defineMetadata('subscriptions', subscriptions = [], target) }

        subscriptions.push({
            methodName,
            targetClass,
            callback: originalMethod
        })
    }
}
导出函数EventHandler(topicKey:any):ClassDecorator{
返回函数(目标:任意){
const subscriptions=Reflect.getMetadata('subscriptions',target.prototype)
const topic=Container.get(topicKey)
主题。订阅(事件=>{
if(subscriptions.length==0){
抛出新错误(`Event received for'${target.constructor.name}'但未定义任何处理程序`)
}
订阅。forEach((订阅:任意)=>{

subscription.callback(event)//问题是装饰器无法访问
类实例。它仅在类定义上计算一次,
目标
是类原型。为了获得类实例,它应该装饰类方法或构造函数(扩展类)然后从它的内部获取
这个

这是的一个特例。
jobCreated
用作回调,因此它应该绑定到上下文。最短的方法是将其定义为箭头:

@Subscribe(JobCreated)
jobCreated = (events: Observable<JobCreated>) => {
    console.log(this) // undefined
}
请注意,订阅发生在
super
之后,这允许在需要时将原始类构造函数中的方法绑定到其他上下文


Reflect
元数据API也可以替换为常规属性,特别是符号。

感谢您的详细响应。尽管尝试了绑定装饰器解决方案,
descriptor.value
在调用bind后返回undefined,因为某些原因两者都不是,但我会尝试类似的方法,谢谢您的时间:)我更新了答案。我希望它能在您的案例中起作用。无论如何,如果您的案例中没有生命周期挂钩,订阅应该在类构造函数中进行,即类应该被扩展。请注意,使用了hasOwnMetadata,即层次结构中的每个类都有自己的
订阅
。因为调用
回调
fr具有子上下文的子类中的om父类是不可取的(具有相同名称的方法可能会被重写,而此调用类似于super[methodName]()),我们应该仅通过方法名来调用它们。很高兴这有帮助。
@Subscribe(JobCreated)
jobCreated = (events: Observable<JobCreated>) => {
    console.log(this) // undefined
}
export function EventHandler (topicKey: any): ClassDecorator {
    return function (target: any) {
        // run only once per class
        if (Reflect.hasOwnMetadata('subscriptions', target.prototype))
            return target;

        target = class extends (target as { new(...args): any; }) {
            constructor(...args) {
                super(...args);

                const topic = Container.get<DomainTopicInterface>(topicKey)
                topic.subscribe(event => {
                    if (subscriptions.length === 0) {
                        throw new Error(`Event received for '${target.constructor.name}'`)
                    }
                    subscriptions.forEach((subscription: any) => {
                        this[subscription.methodName](event); // this is available here
                    })
                })
            }
        } as any;


export function Subscribe (targetClass: StaticDomainEvent<any>): MethodDecorator {
    return function (target: any, methodName: string, descriptor: TypedPropertyDescriptor<any>) {
        // target is class prototype
        let subscriptions = Reflect.getOwnMetadata('subscriptions', target);

        subscriptions.push({
            methodName,
            targetClass
            // no `callback` because parent method implementation
            // doesn't matter in child classes
        })
    }
}