Определите динамически сгенерированные свойства из унаследованного класса
Я пытаюсь создать 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