Я собираюсь интерпретировать этот вопрос строго как "как я могу дать типизацию существующему коду, чтобы компилятор понимал код в разделе Usage
?" Таким образом, ответ не должен касаться выдаваемого JavaScript, но вместо этого должен изменять только определения типов, аннотации и утверждения.
В сторону: более общий вопрос «как мне реализовать класс, экземпляры которого сами являются конструкторами классов» - я не буду пытаться ответить, так как из моих исследований лучший ответ здесь - «не пытайтесь сделать это ", поскольку он плохо работает с прототипом модели наследования в JS. Вместо этого я бы предпочел, чтобы экземпляр неконструктивного класса содержал свойство, являющееся конструктором нового класса. Что-то вроде этого кода детской площадки . Я ожидаю, что вы были бы намного счастливее в долгосрочной перспективе.
Возвращаясь к типам: основная проблема здесь в том, что TypeScript не может указать, что конструктор класса возвращает тип, отличный от класса, являющегося классом. определены. Это либо намеренно (см. microsoft / TypeScript # 11588 , либо отсутствует функция (см. microsoft / TypeScript # 27594 ), но в любом случае это не является частью языка.
Что мы можем сделать здесь, это использовать объявление слияния . Когда вы пишете class Model {}
, вы вводите как объект конструктора класса с именем Model
, так и тип интерфейса с именем Model
Этот интерфейс можно объединить, добавив методы и свойства, о которых компилятор еще не знает. В вашем случае вы можете сделать это:
interface Model {
new(object: {}): DocumentCarrier;
Model: Model;
}
Это позволит компилятору узнать, что Model
экземпляров, помимо того, что свойства / методы объявлены в классе, также имеется свойство Model
, тип которого Model
, и, что важно, сигнатура конструктора. Этого достаточно, чтобы следующий код компилировался без ошибок:
const User = new Model("User");
const user = new User({ "id": 5, "name": "Bob" });
user.save(); // "Saved document {"id": 5, "name": "Bob"} to User"
console.log(User.Model.myName); // "User"
User.get(5); // "Retrieving item with id = 5"
Компилятор считает, что User.myName
существует, чего нет во время выполнения, но это уже проблема с существующим кодом, поэтому я не буду касаться этого здесь , Можно дополнительно изменить типизацию, чтобы компилятор знал, что User.Model.myName
существует и что User.myName
не существует, но это становится довольно сложным, поскольку требуется разделить интерфейс Model
на несколько типов, которые вы тщательно присваиваете на правильные значения. Поэтому сейчас я игнорирую это.
Единственное другое изменение, которое я здесь сделаю, - это добавление различных типов к реализации Model
, например:
class Model {
myName: string;
Document: Model;
get!: (id: number) => void;
constructor(name: string) {
this.myName = name;
const self: Model = this;
class Document extends DocumentCarrier {
static Model: Model;
constructor(object: {}) {
super(self, object);
}
}
Document.Model = self;
(Object.keys(Object.getPrototypeOf(this)) as
Array<keyof typeof DocumentCarrier>).forEach((key) => {
Document[key] = this[key].bind(this);
});
this.Document = Document as Model;
return this.Document;
}
}
Единственное, что компилятор не сможет проверить в вышеприведенном, это то, что класс Document
является допустимым Model
, поэтому мы используем утверждение Document as Model
. Кроме этого, я просто поставил несколько утверждений (get
- это , определенно назначенный , а Object.keys()
вернет массив ключей конструктора DocumentCarrier
), чтобы вам не нужно было отключать --strict
флаг компилятора.
Хорошо, надеюсь, это поможет. Удачи!
Детская площадка ссылка на код