Определите динамически сгенерированные свойства из унаследованного класса

Я пытаюсь создать d.ts файл декларации для моего проекта.

Есть два класса, которые вычисляют тяжелую логику, а остальные классы наследуются от них. Свойства подклассов определяются не для самого объекта, а для getter названный defaults внутри класса и определяется во время выполнения.

Базовые модели

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> {}

Как добавить определение свойства к унаследованным классам, не определяя их вручную?

1 ответ

Решение

Вы не можете сделать это напрямую, вы можете сделать это, если вы создадите класс, используя дополнительную функцию, которая будет мутировать создаваемый класс для добавления свойств:

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;
}

Обратите внимание, что в параметрах отдыха и выражениях выражений для функции версии 2.9, 2.8 используются кортежи 3.0. ReplaceInstanceType увидеть этот ответ

Мы можем использовать это одним из двух способов:

Используйте вывод 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

Это имеет тот недостаток, что дополнительные поля не могут использоваться внутри самого класса, что может быть проблемой в некоторых ситуациях.

Второе использование заключается в использовании createModel в предложении extends нового класса и задайте только значения по умолчанию в классе, который мы передаем extends, добавляя дополнительные методы во внешний класс:

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 класса: внешний, который мы фактически используем, и внутренний, который мы используем для добавления свойства по умолчанию.

редактировать

Так как машинопись действительно очень хорошо справляется со строгой типизацией и проверкой имен ключей, вы также можете рассмотреть возможность использования 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
Другие вопросы по тегам