TypeScript覆盖扩展类方法,该方法使用装饰器会返回旧方法。

huangapple go评论62阅读模式
英文:

TypeScript overriding extended class method that uses decorator gives old method

问题

To assign the actual value that calls "child" in this context, you can use an arrow function to preserve the context. Here's how you can modify the setTimeout line:

setTimeout(() => a.doSomething(), 1000);

This change ensures that the doSomething method is called within the correct context of the child object, resulting in the output "child" as expected.

英文:

That gives the old value of the method before being overridden. How I can get the final value there?

const a = {} as any

class Parent {
    @D()
    public doSomething(b: any) {
        console.log('parent')
    }
}

class Child extends Parent{
    public override doSomething() {
        console.log('child')
    }
}

const child = new Child()
child.doSomething()

function D () {
    return function(target: any, property: string, descriptor: PropertyDescriptor) {
        console.log('assign')
        a[property] = target[property]
    }
}

setTimeout(a.doSomething, 1000)   // here it gives "parent" but it should be "child"

Here is the demo

How I can assign the actual value there, the one that calls "child"?

答案1

得分: 0

在接下来的内容中,我将使用JavaScript装饰器,这在TypeScript 5.0中开始支持。我不会花时间查看由问题中的代码表示的旧/实验性TypeScript装饰器。旧装饰器实际上并没有被弃用,但它们不再开发,最终使用它们将变得比避免使用它们更困难。


问题中的代码存在两个一般性问题,阻止其按预期工作。

首先:装饰器函数本身每个方法声明调用一次,而不是每个方法调用一次。因此,原始代码仅设置了a.doSomething一次。如果您希望每次调用装饰的方法时都发生某些事情,您需要使装饰器包装该方法。因此,装饰器函数被调用一次,并且它利用这个机会修改它装饰的方法:

const a = {} as any   
function D(originalMethod: any, context: ClassMethodDecoratorContext) {
    const methodName = String(context.name);
    function replacementMethod(this: any, ...args: any[]) {
        a[methodName] = this[methodName].bind(this);
        const result = originalMethod.call(this, ...args);
        return result;
    }
    return replacementMethod;
}

class Parent {
    @D
    public doSomething() {
        console.log('parent')
    }
}

其次,没有合理的方法修改子类方法,以覆盖已装饰的超类方法的行为。覆盖方法仅通过存在而与其超类方法互动。因此,如果您希望子类方法调用触发已装饰的行为,您可以明确装饰子类方法本身:

class Child extends Parent {
    @D // <-- 明确装饰子类方法
    public override doSomething() {
        console.log('childDecorateSub');
    }
}

const child = new Child();
child.doSomething(); // "childDecorateSub" 
a.doSomething(); // "childDecorateSub" 

或者您将不得不在super上调用已装饰的方法:

class Child extends Parent {
    public override doSomething() {
        console.log('childSuperCall');
        super.doSomething(); // <-- 明确调用
        // 已装饰的超类方法
    }
}

const child = new Child()
child.doSomething(); // "childSuperCall", "parent" 
a.doSomething(); // "childSuperCall", "parent" 

这取决于您希望看到的行为。但其中一个应该能满足您的需求。


请注意,我说的是子类方法从超类方法继承装饰器的“没有合理的方法”。从技术上讲,可能存在一些疯狂的代理超类原型的方式,使得当您创建子类时,它会自动重新装饰方法,但我不想开始实现这样的东西,因为它很可能非常脆弱。

代码的Playground链接

英文:

In what follows I will be using JavaScript decorators which are supported starting with in TypeScript 5.0. I won't spend time looking at legacy/experimental TypeScript decorators as represented by the code in the question. Legacy decorators aren't actually deprecated, but they are not going to be developed anymore and eventually they will be harder to use than to avoid.


There are two general issues with the code in the question that prevent it from working as intended.

First: the decorator function itself is called once per method declaration, not per method call. So the original code only sets a.doSomethingonce. If you want something to happen every time a decorated method is called, you will need your decorator to wrap the method. So the decorator function is called once, and it uses that opportunity to modify the method it's decorating:

const a = {} as any   
function D(originalMethod: any, context: ClassMethodDecoratorContext) {
    const methodName = String(context.name);
    function replacementMethod(this: any, ...args: any[]) {
        a[methodName] = this[methodName].bind(this);
        const result = originalMethod.call(this, ...args);
        return result;
    }
    return replacementMethod;
}

class Parent {
    @D
    public doSomething() {
        console.log('parent')
    }
}

Next, there's no reasonable way to modify the behavior of subclass methods that override a decorated superclass method. Overriding methods don't interact with their superclass method merely by existing. So if you want a subclass method call to trigger the decorated behavior, you'll either need to decorate the subclass method itself:

class Child extends Parent {
    @D // <-- explicitly decorate subclass method
    public override doSomething() {
        console.log('childDecorateSub');
    }
}

const child = new Child();
child.doSomething(); // "childDecorateSub" 
a.doSomething(); // "childDecorateSub" 

Or you'll have to call the decorated method on super

class Child extends Parent {
    public override doSomething() {
        console.log('childSuperCall');
        super.doSomething(); // <-- explicitly call 
        // decorated superclass method
    }
}

const child = new Child()
child.doSomething(); // "childSuperCall", "parent" 
a.doSomething(); // "childSuperCall", "parent" 

It depends on the behavior you want to see. But one of those should hopefully work for you.


Notice I said "no reasonable way" for subclass methods to inherit the decorator from superclass methods. It might be technically possible to do some crazy proxying of the superclass's prototype so that when you make a subclass it automatically re-decorates methods, but I wouldn't want to begin implementing something like that, since it's likely to be incredibly fragile.

Playground link to code

huangapple
  • 本文由 发表于 2023年5月10日 20:41:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76218559.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定