如何在 TypeScript 中使用装饰器正确包装构造函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34411546/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:10:26  来源:igfitidea点击:

How to properly wrap constructors with decorators in TypeScript

typescript

提问by thedayturns

The process of wrapping a class with a decorator causes superclasses to be unable to access that classes' properties. Why?

用装饰器包装类的过程会导致超类无法访问该类的属性。为什么?

I have some code that:

我有一些代码:

  1. Creates a decorator which replaces the constructor of a class with a new constructor that should do the exact same thing.
  2. Creates a base class with a property.
  3. Wraps the base class with the wrapping decorator.
  4. Creates a class that extends the base class.
  5. Tries to access the property on the extended class. This is the part that fails.
  1. 创建一个装饰器,用一个新的构造函数替换一个类的构造函数,该构造函数应该做完全相同的事情。
  2. 创建具有属性的基类。
  3. 用包装装饰器包装基类。
  4. 创建一个扩展基类的类。
  5. 尝试访问扩展类上的属性。这是失败的部分。

Here is the code:

这是代码:

function wrap(target: any) {
  // the new constructor
  var f: any = function (...args) {
      return new target();
  }

  f.prototype = target.prototype;
  return f;
}

@wrap
class Base {
    prop: number = 5;
}

class Extended extends Base {
    constructor() {
        super()
    }
}

var a = new Extended()
console.log(new Extended().prop) // I'm expecting 5 here, but I get undefined.

I'm sure this is some nuance of either prototypes in general or the specific way that TypeScript handles them that I do not grasp.

我确信这是一般原型或 TypeScript 处理它们的特定方式的一些细微差别,我不了解。

采纳答案by TSV

This code worksfor me:

此代码的工作对我来说:

function logClass(target: any) {
  // save a reference to the original constructor
  var original = target;

  // the new constructor behaviour
  var f : any = function (...args) {
    console.log("New: " + original.name); 
    //return  original.apply(this, args);
    return new original(...args); // according the comments
  }

  // copy prototype so intanceof operator still works
  f.prototype = original.prototype;

  // return new constructor (will override original)
  return f;
}

@logClass
class Base {
    prop: number = 5;
}

class Extended extends Base {
    constructor() {
        super()
    }
}

var b = new Base()
console.log(b.prop)

var a = new Extended()
console.log(a.prop)

回答by etech

This is the more modern approach using the latest TS (3.2.4). The below also uses the decorator factory pattern so you can pass in attributes:

这是使用最新 TS (3.2.4) 的更现代的方法。下面还使用了装饰器工厂模式,因此您可以传入属性:

function DecoratorName(attr: any) {
  return function _DecoratorName<T extends {new(...args: any[]): {}}>(constr: T){
    return class extends constr {
      constructor(...args: any[]) {
        super(...args)
        console.log('Did something after the original constructor!')
        console.log('Here is my attribute!', attr.attrName)
      }
    }
  }
}

See here for more info: https://www.typescriptlang.org/docs/handbook/decorators.html#class-decorators

有关更多信息,请参见此处:https: //www.typescriptlang.org/docs/handbook/decorators.html#class-decorators

回答by urish

A solution using ES2015 Proxy to override the constructor:

使用 ES2015 Proxy 覆盖构造函数的解决方案:

function wrap(target: any) {
  return new Proxy(target, {
    construct(clz, args) {
      console.log(`Constructing ${target.name}`);
      return Reflect.construct(clz, args);
    }
  });
}

@wrap
class Base {
  prop: number = 5;
}

class Extended extends Base {
  constructor() {
    super()
  }
}

var a = new Extended()
console.log(new Extended().prop);

You can also run this on StackBlitz

你也可以在 StackBlitz 上运行它

回答by PhiLho

The comments in the other answers complain that code doesn't work.
Actually, it works, but not in jsFiddle...
It is an issue with the code generation in jsFiddle (perhaps using an obsolete version of TypeScript).
The code above works with TypeScript 2.7.2 (run with Node).

其他答案中的评论抱怨代码不起作用。
实际上,它有效,但在 jsFiddle 中无效...
这是 jsFiddle 中代码生成的问题(可能使用的是过时的 TypeScript 版本)。
上面的代码适用于 TypeScript 2.7.2(使用 Node 运行)。

So this is basically the code in pablorsk's answer (except there is no need to return the instance), I just added full types to please a stricter TSLint...

所以这基本上是pablorsk的答案中的代码(除非不需要返回实例),我只是添加了完整类型以取悦更严格的TSLint ...

function logClass<T extends { new(...args: any[]): {} }>(): any {
    type Ctor = new (...args: any[]) => T;
    return (target: T): Ctor => {
        // Save a reference to the original constructor
        const Original = target;

        // the new constructor behaviour
        let decoratedConstructor: any = function (...args: any[]): void {
            console.log("Before construction:", Original);
            Original.apply(this, args);
            console.log("After construction");
        };

        // Copy prototype so intanceof operator still works
        decoratedConstructor.prototype = Original.prototype;
        // Copy static members too
        Object.keys(Original).forEach((name: string) => { decoratedConstructor[name] = (<any>Original)[name]; });

        // Return new constructor (will override original)
        return decoratedConstructor;
    };
}

@logClass()
class Base {
    prop = 5;
    constructor(value: number) {
        console.log("Base constructor", value);
        this.prop *= value;
    }
    foo() { console.log("Foo", this.prop); }
    static s() { console.log("Static s"); }
}

class Extended extends Base {
    constructor(init: number) {
        super(init);
        console.log("Extended constructor", init);
    }
    bar() { console.log("Bar", this.prop); }
}

const b = new Base(2);
console.log("Base", b instanceof Base);
b.foo();
Base.s();

const e = new Extended(5);
console.log("Extended", e instanceof Base, e instanceof Extended);
e.bar();

[EDIT] Also added a line copying static members, otherwise decorated class throws an error when calling the static method.

[编辑] 还添加了一行复制静态成员,否则装饰类在调用静态方法时会抛出错误。

回答by pablorsk

If you like run code after and before constructor() with a decorator:

如果您喜欢在构造函数() 之后和之前使用装饰器运行代码

function ClassWrapper() {
    return function(target: any) {
        // save a reference to the original constructor
        var original = target;

        // the new constructor behaviour
        var f: any = function (...args) {
            console.log('ClassWrapper: before class constructor', original.name);
            let instance = original.apply(this, args)
            console.log('ClassWrapper: after class constructor', original.name);
            return instance;
        }

        // copy prototype so intanceof operator still works
        f.prototype = original.prototype;

        // return new constructor (will override original)
        return f;
    };
}
@ClassWrapper()
export class ClassExample {
    public constructor() {
        console.info('Running ClassExample constructor...');
    }
}

let example = new ClassExample();

/*
CONSOLE OUTPUT:
ClassWrapper: before class constructor ClassExample
Running ClassExample constructor...
ClassWrapper: after class constructor ClassExample
*/