通过 TypeScript 中的装饰器向 class 添加属性

Adding properties to a class via decorators in TypeScript

在 TypeScript 的装饰器参考页面上,有一段代码片段说明了如何使用 class 装饰器覆盖构造函数:

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

在日志中:

class_1 {
  property: 'property',
  hello: 'override',
  newProperty: 'new property' }

到目前为止一切顺利。但是尝试通过点符号访问 newProperty 失败:

Property 'newProperty' does not exist on type 'Greeter'.ts(2339)

错误,它没有在 VS Code 的提示中列出。可以通过括号表示法访问它,但 TS 警告说

Element implicitly has an 'any' type because type 'Greeter' has no index signature.ts(7017)

我错过了什么吗?如何以类型安全的方式通过装饰器实现添加新属性?我希望像普通 class 成员一样获得正常的编译器支持。

设计上的装饰器无法更改 class 的类型。这仍在讨论中,直到装饰器提案最终确定 team will not change the behavior. You can use mixins for this task (read about mixins in ts)

使用 mixins 代码看起来像这样:

function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

const Greeter = classDecorator(class {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
});
type Greeter = InstanceType<typeof Greeter> // have the instance type just as if we were to declare a class

console.log(new Greeter("world").newProperty);
function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}
interface classInterface {
    newProperty: string;
    hello: string;
}

//trick
interface Greeter extends classInterface { };

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}
const b = new Greeter();
console.log(b.newProperty);

看来我们可以使用界面技巧来解决这个问题。 套路参考:

不是直接的解决方案,而是避免装饰器限制的变通方法: 在某些情况下,可以用可以正确继承类型的普通 class 替换装饰器。

class ClassDecorator {
        newProperty = "new property";
        hello = "override";
}

class Greeter extends ClassDecorator {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));