从继承的 class 定义动态生成的属性

Define dynamically generated properties from an inherited class

我正在尝试为我的项目生成一个 d.ts 声明文件。

有两个class计算重逻辑,其余class继承自它们。 subclasses 上的属性不是在对象本身上定义的,而是在 class 中名为 defaultsgetter 上定义的,并在运行时定义。

基础模型

abstract class BaseModel {}
abstract class Model extends BaseModel {}

继承模型

class Example extends Model {
    get defaults() {
       return {
            someProp: 1,
            anotherProp: 2
       }
    }
}

我的代码运行完美,但没有自动完成 动态添加的属性。我尝试将以下内容添加到 d.ts 文件以使其了解动态属性,但它似乎不起作用。

index.d.ts

class Model<T> extends BaseModel {
    [P in keyof T]: T[P]
}

type ExampleType = {
    someProp : number
    anotherProp : number
}

class Example extends Model<ExampleType> {}

如何将 属性 定义添加到继承的 class 中而无需手动定义它们?

你不能直接这样做,如果你创建 class 使用一个额外的函数,你可以这样做,这将改变创建 class 以添加属性:

type ReplaceInstanceType<T extends new (...args: any[])=> any, TNewInstance> = 
    T extends new (...args: infer U)=> any ? 
        new (...args: U) => TNewInstance : 
        never;

function createModel<T extends new (...args: any[])=> any>(modelClass: T) : 
    ReplaceInstanceType<T, InstanceType<T> & InstanceType<T>['defaults']> {
    return modelClass as any;
}

注意 这使用了 3.0 功能 Tuples in rest parameters and spread expressions 对于 ReplaceInstanceType 的 2.9、2.8 版本,请参阅

我们可以通过以下两种方式之一使用它:

直接使用createModel的输出:

const Example = createModel(class extends Model {
    constructor (data: string) {
        super();
        // this.anotherProp // Not ok we can't access teh extra fields inside the class
    }
    get defaults() {
        return {
            someProp: 1,
            anotherProp: 2
        }
    }
});
new Example(``).anotherProp // we can create the class and access the extra fields
new Example("").someProp

这有一个缺点,即在 class 本身中无法使用额外的字段,这在某些情况下可能是个问题。

第二种用法是在新的class的extends子句中使用createModel,只在传递给extends的class中定义默认值,在外部 class:

class Example extends createModel(class extends Model {
    get defaults() {
        return {
            someProp: 1,
            anotherProp: 2
        }
    }
}) {
    constructor(data: string) {
        super();
        this.anotherProp // new properties are accesible
    }
};
new Example(``).anotherProp // we can create the class and access the extra fields
new Example("").someProp

这种方法的缺点是实际上创建了 2 个 class 我们实际使用的外部方法和我们用来添加默认值的内部方法 属性。

编辑

由于 typescript 实际上在强类型和验证键名方面做得很好,您也可以考虑使用 get/set 方法,您可以获得良好的代码完成和类型验证,但它有点冗长:

abstract class BaseModel { 
    abstract get defaults();
    get<K extends keyof this['defaults']>(name: keyof this['defaults']): this['defaults'][K]{
        return this[name] as any;
    }
    set<K extends keyof this['defaults']>(name: keyof this['defaults'], value:  this['defaults'][K]) : void{
        this[name] =  value;
    }
}

class Example extends BaseModel {
    get defaults() {
        return {
            someProp: 1,
            anotherProp: 2
        }
    }

};
new Example().get('anotherProp')
new Example().get("someProp") 
new Example().set("someProp", 1) // 
new Example().set("someProp", "1") // error wrong type
new Example().get("someProp2")  // error