Полиморфный тип this
Как в предке описать тип потомка и при чем здесь полиморфный тип this
- сможет ответить текущая глава.
this - как тип
В TypeScript существует возможность указывать в качестве типа полиморфный тип this
.
Полиморфный тип — это тип, который представляет множество типов, как единое целое.
Полиморфный тип this
является совокупностью типов, определяющих тип экземпляра. Кроме того, полиморфный тип this
доступен для указания только в классах и интерфейсах.
Чтобы понять о чем идет речь, нужно представить два класса, связанных с помощью механизма наследования и в каждом из них объявлен метод, возвращаемое значение которого принадлежит к типу представляющего экземпляр класса, в котором он определен.
class Animal { public sit(): Animal { // реализация метода return this; } } class Bird extends Animal { public fly(): Bird { // дополнение супертипа специфичным методом return this; } }
Если создать экземпляр подкласса и вызвать по цепочке метод подкласса, а затем — суперкласса, то операция завершится успехом.
const bird: Bird = new Bird() .fly() // Ok, возвращает тип Bird .sit(); // Ok, возвращает тип Animal
Если попробовать изменить порядок вызова методов, то возникнет ошибка. Произойдет это по той причине, что метод, объявленный в суперклассе, возвращает значение, принадлежащее к типу самого суперкласса, который ничего не знает о методах, объявленных в его подтипах.
const bird: Bird = new Bird() .sit() // Ok, возвращает тип Animal .fly(); // Error, в типе Animal, возвращенного на предыдущем шаге, нет объявления метода fly
Можно, конечно, в качестве возвращаемого значения указать тип any
, но помимо того, что пропадет автодополнение, ещё и пострадает семантическая привлекательность кода.
class Animal { public sit(): any { return this; } } class Bird extends Animal { public fly(): any { return this; } } let bird: Bird = new Bird() .fly() // Ok .sit(); // Ok bird = new Bird() .sit() // Ok .fly(); // Ok, работает, но так лучше не делать
TypeScript предлагает решить эту проблему с помощью полиморфного типа this
. Ожидаемое поведение достигается за счет того, что полиморфный тип this
является множеством типов, определяемого цепочкой наследования. Другими словами, тип this
будет принадлежать к тому же типу, что и экземпляр подкласса, который принадлежит к типу подкласса и типу суперкласса одновременно.
Такое поведение называется F-ограниченный полиморфизм (F-bounded polymorphism).
class Animal { public sit(): this { return this; } } class Bird extends Animal { public fly(): this { return this; } } let bird = new Bird() .fly() // Ok .sit(); // Ok bird = new Bird() .sit() // Ok, возвращает тип Bird .fly(); // Ok
Стоит отдельно подчеркнуть, что полиморфный тип this
не принадлежит к типу класса или интерфейса в котором указан. Полиморфный тип this
может быть определен только на основе экземпляра класса. Проще говоря, полиморфный тип this
принадлежит к типу своего экземпляра и может быть определен только в момент создания экземпляра. Так же тип this
совместим с типом any
, а при условии, что флаг --strictNullChecks
установлен в false
, ещё и к типам null
и undefined
. К тому же тип this
совместим с типом экземпляра, ссылку на который можно получить с помощью ключевого слова this
.
class Animal { public animalAll: this[] = []; // массив с полиморфным типом this constructor() { this.add(new Animal()); // Error, так как на данном этапе не известно, к какому типу будет принадлежать полиморфный тип this this.add(this); // Ok } public add(animal: this): this { this.animalAll.push(animal); return this; } } class Type { static interface: typeof Animal = Animal; static animal: Animal = new Animal(); static any: any = new Animal(); static null: null = null; static undefined: undefined = undefined; } const animal = new Animal() .add(Type.animal) // Ok .add(Type.interface) // Error .add(Type.any) // Ok .add(Type.null) // Ok .add(Type.undefined); // Ok
Не будет лишним упомянуть, что в тех случаях, когда тип не указан явно, а в качестве значения выступает ссылка на экземпляр (this
), то вывод типов будет указывать на принадлежность к полиморфному типу this
.
class Animal { public instance = this; // instance: this public getInstance() { // getInstance(): this const instance = this; // instance: this return this; } }