Совместимость объектных типов¶
На практике очень много недопониманий связано с темой совместимости объектных типов, постижение которой возможно лишь путем последовательного рассмотрения каждого отдельного случая в разных ситуациях. Именно этому и будет посвящена текущая глава, которая также уделит немало внимания другим сопутствующим нюансам.
Важно¶
Пришло время более подробно разобраться, как компилятор определяет совместимость объектных типов. Как всегда, вначале, стоит напомнить, что в текущей главе будет использоваться шаблон (: Target = Source
), о котором более подробно шла речь в самом начале.
Но прежде чем начать погружение в тему совместимости типов (compatible types), будет не лишним заметить, что подобный термин не определен спецификацией TypeScript. Тем не менее, в TypeScript описано два типа совместимости. Помимо привычной совместимости подтипов (assignment subtype), также существует совместимость при присваивании (assignment compatibility). Они отличаются только тем, что правила совместимости при присваивании расширяют правила совместимости подтипов. Сделано это по нескольким причинам.
Прежде всего поведение типа any
не укладывается в рамки, определяемые стандартными правилами. Нестандартное поведение заключается в том, что помимо совместимости всех типов на основе обычных правил совместимости с типом any
, сам тип any
также совместим со всеми, не являясь их подтипом.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Кроме того, поведением двухсторонней совместимости наделен и числовой enum
.
1 2 3 4 5 6 |
|
Совместимость объектных типов в TypeScript¶
Начать тему о совместимости объектных типов стоит с повторения определения структурной типизации, которая лежит в основе TypeScript. Итак, структурная типизация — это механизм сопоставления двух типов на основе их признаков. Под признаками понимаются идентификаторы типа и типы, которые с ними связаны (ассоциированы).
Простыми словами, два типа будут считаться совместимыми не потому, что они связаны иерархическим деревом (наследование), а потому, что в типе S
(: Target = Source
) присутствуют все идентификаторы, присутствующие в типе T
. При этом, типы, с которыми они ассоциированы, должны быть совместимы.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
В случаях, когда один тип, помимо всех признаков второго типа, также имеет любые другие, то он будет совместим со вторым типом, но не наоборот. Для обратной совместимости потребуется операция явного преобразования (приведения) типов.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Кроме того, два типа, совместимые по признакам идентификаторов, будут совместимы только в том случае, если типы, ассоциированные с идентификаторами, также совместимы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Стоит заметить, что методы, объявленные в объектном типе, сравниваются не по правилам совместимости объектных типов данных. Про правила проверки функциональных типов речь пойдет немного позднее (глава Совместимость функций). Поэтому комментарии к коду будут опущены.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
По этой же причине без подробного рассмотрения останется и следующий пример, в котором происходит проверка типов, содержащих перегруженные методы, поскольку их совместимость идентична совместимости функциональных типов, рассматриваемых в следующей главе. Сейчас стоит только упомянуть, что в случаях, когда функция перегружена, проверка на совместимость происходит для каждой из сигнатур. Если существует несколько вариантов перегруженных сигнатур, с которыми может быть совместим тип источник, то выбрана будет та, что объявлена раньше.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Типы, которые различаются только необязательными членами, также считаются совместимыми.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Дело в том, что необязательные параметры в объектных типах не берутся в расчет при проверке на совместимость. Однако это правило действует только в одну сторону. Тип, содержащий обязательный член, несовместим с типом, у которого идентичный член является необязательным. Такое поведение логично, ведь в случае, когда необязательный член будет отсутствовать, тип, содержащий его, не будет удовлетворять условиям, заданным типом с обязательным членом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Существует еще одна неочевидность, связанная с необязательными членами. Если в целевом типе все члены объявлены как необязательные, он будет совместим с любым типом, который частично описывает его, при этом тип источник может описывать любые другие члены. Помимо этого он будет совместим с типом, у которого описание отсутствует вовсе. Но он не будет совместим с типом, у которого описаны только отсутствующие в целевом типе члены. Такое поведение в TypeScript называется Weak Type Detection (обнаружение слабого типа). Типы, описание которых состоит только из необязательных членов, считаются слабыми типами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Обобщенные типы, закрытые частично или полностью, участвуют в проверке на совместимость по характерным для TypeScript правилам.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
В случаях, когда на совместимость проверяются типы, содержащие обобщенные методы, то их сравнение ничем не отличается от сравнения типов, содержащих необобщенные методы.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
На фоне структурной типизации самое неоднозначное поведение возникает, когда описание типов полностью идентично, за исключением их модификаторов доступа. Если в типе описан хоть один член с отличным от public
модификатором доступа, он не будет совместим ни с одним схожим типом, независимо от того, какие модификаторы доступа применены к его описанию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
К счастью, разногласия, возникающие в структурной типизации при совместимости типов, представляемых классами, к членам которых применены модификаторы доступа, отличные от public
, не распространяются на номинативную типизацию (глава Совместимость типов на основе вида типизации). Номинативная типизация может указывать на принадлежность к типу через иерархию наследования. Простыми словами, потомки будут совместимы с предками, у которых члены объявлены с помощью модификаторов доступа, отличных от public
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
В типах, определяемых классами, при проверке на совместимость не учитываются конструкторы и статические члены (члены класса).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Когда в качестве присваиваемого типа выступает экземпляр класса, то для того, чтобы он считался совместим с типом, указанным в аннотации, в нем как минимум должны присутствовать все признаки этого типа. Также он может обладать дополнительными признаками, которые отсутствуют в типе, указанном в аннотации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Однако, когда в качестве значения выступает объектный тип, созданный с помощью объектного литерала, поведение в некоторых случаях отличается от поведения присвоения экземпляров класса. В тех случаях, в которых объект объявляется непосредственно в операции присвоения, он будет совместим с типом, указанным в аннотации только если он полностью ему соответствует. Другими словами, создаваемый с помощью литерала объект не должен содержать ни меньше ни больше членов, чем описано в типе, указанном в аннотации (данное поведение можно изменить с помощью опции компилятора --suppressExcessPropertyErrors
, глава “Опции компилятора”).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Остается только добавить, что выбор в сторону структурной типизации был сделан по причине того, что подобное поведение очень схоже с поведением самого JavaScript, который реализует утиную типизацию. Можно представить удивление Java или C# разработчиков, которые впервые увидят структурную типизацию на примере TypeScript. Сложно представить выражение лица заядлых теоретиков, когда они увидят, что сущность птицы совместима с сущностью рыбы. Но не стоит нагнетать ситуацию, выдумывая нереальные примеры, которые из-за структурной типизации приведут к немыслимым последствиям, поскольку вероятность того, что хотя бы один из них найдет олицетворение в реальных проектах настолько мала, что не стоит сил, затраченных на их выдумывание.