使用类装饰器来更改子类方法

时间:2019-10-20 05:45:52

标签: typescript

如何使用类修饰符来更改修饰后的类的子类的方法? 这是更改类自己的方法的示例,但不适用于子类自己的方法:

export function guardAllNonConstructorOwnMethodsAgainstBadArguments(
  constructor: Function
) {
  const badArguments = new Set(['', undefined, null]);
  const prototype = constructor.prototype;
  Object.getOwnPropertyNames(prototype)
    .filter(
      ownPropertyName =>
        typeof prototype[ownPropertyName] === 'function' &&
        ownPropertyName !== 'constructor'
    )
    .forEach(propertyName => {
      const nonConstructorOwnMethod = prototype[propertyName];
      prototype[propertyName] = function(...args: any[]) {
        const everyArgumentIsGood = args.every(arg => !badArguments.has(arg));
        if (everyArgumentIsGood) {
          return nonConstructorOwnMethod.bind(this)(...args);
        }
      };
    });
}

即,对于此示例,如何修改此装饰器,以使该装饰器所应用的类的子类的方法也免受“错误参数”的侵害?

1 个答案:

答案 0 :(得分:1)

在类定义时,类装饰器被调用一次。它们不作用于子类。如果要装饰子类,则可能应该装饰每个子类。

据我所知,在子类碰巧扩展已经装饰的超类时,没有全局钩子可以设置为触发代码运行。我能想到的最接近的做法是让子类在创建第一个实例时调用您的代码。也就是说,class Subclass extends Superclass {}不会触发任何内容,但是new Subclass()会触发代码,而下一个new Subclass()不会触发代码。对您来说可能已经足够了。这是我可以想象的一种方式:

const callForThisClassAndForEachSubclass =
    (cb: ((ctor: new (...args: any) => void) => void)) =>
        (ctor: new (...args: any) => any) => {
            const registry = new Set<new (...args: any) => any>();
            const alreadyDecorated = Symbol();
            const {[ctor.name]: newCtor} = {
                [ctor.name]: class extends ctor {
                    constructor(...args: any) {
                        super(...args);
                        const thisCtor = this.constructor as new (...args: any) => any;
                        if (!registry.has(thisCtor)) {
                            cb(thisCtor);
                            registry.add(thisCtor);
                        }
                    }
                }
            };
            cb(newCtor);
            registry.add(newCtor);
            return newCtor;
        };

这将创建一个构造函数注册表,并且对于它看到的每个构造函数仅调用一次回调。我们必须重写超类的构造函数,以使其在构造子类时发生。它看起来很复杂,但让我们看看它的作用:

const log = (ctor: new (...args: any) => any) => {
    console.log("I'm decorating this", ctor);
}

console.log("before superclass declaration")  
@callForThisClassAndForEachSubclass(log)
class Superclass {

} // I'm decorating this function Superclass()
new Superclass(); // nothing

console.log("before subclass declaration")
class Subclass extends Superclass {
} // nothing

console.log("before subclass instance")
new Subclass(); // I'm decorating this function Subclass()
console.log("before subclass instance")
new Subclass(); // nothing

console.log("before subclass2 declaration")
class Subclass2 extends Superclass {
} // nothing

console.log("before subclass2 instance")
new Subclass2(); // I'm decorating this function Subclass2()
console.log("before subclass2 instance")
new Subclass2(); // nothing

console.log("before subsubclass declaration")
class SubSubclass extends Subclass {
} // nothing

console.log("before subsubclass instance")
new SubSubclass(); // I'm decorating this function SubSubclass2()
console.log("before subsubclass instance")
new SubSubclass(); // nothing

您可以看到,函数log()在类创建时为SuperClass调用了一次,而在创建第一个实例时为每个子类调用了一次。如果您使用log函数而不是guardAllNonConstructorOwnMethodsAgainstBadArguments,它可能会为您解决问题(特别是因为这似乎只涉及方法),但是我不确定因为问题不包括预期的用例。

无论如何,即使它不起作用,也可能会帮助您想到一个替代方案,该方案基于在调用子类构造函数时重写超类构造函数以执行自定义操作。祝你好运!

Link to code