Вывод типов¶
Понимание темы, относящейся к такому фундаментальному механизму как вывод типов, поможет разработчику подчинить компилятор tsc, а не наоборот. Невозможно писать программы на языке TypeScript, получая от процесса удовольствие, если нет однозначного ответа на вопрос "указывать типы явно или нет". Ответы на этот и другие сопряженные вопросы как раз и содержит данная глава, посвященная подробному рассмотрению каждого момента.
Вывод типов - общие сведения¶
Чтобы не повторять определения, которые были даны в главе “Экскурс в типизацию - Связывание, типизация, вывод типов”, эту главу стоит начать с неформального определения.
Вывод типов — это механизм, позволяющий сделать процесс разработки на статически типизированном TypeScript более простым за счет перекладывания на него рутинной работы по явной аннотации типов. Может показаться, что вывод типов берется за дело только тогда, когда при разборе кода попадается отсутствующая аннотация типа. Но это не так. Компилятор не доверяет разработчику, и весь код в штатном режиме проходит через вывод типов. Не важно, в полной мере разработчик указывает типы определяемым им конструкциям или нет, чтобы их проверить на адекватность и совместимость, вывод типов обязан создать для них собственное описание (понимать как объявление типа).
В этом механизме нет ничего сложного, но, несмотря на это, у начинающих разработчиков TypeScript некоторые неочевидные особенности могут вызвать вопросы, главным из которых является уже упомянутый в самом начале - "указывать явно типы или нет".
Ответ очевиден и прост - во всех случаях, допускающих отсутствие явной аннотации типа, эту работу стоит поручать выводу типов. Другими словами, не нужно указывать типы явно, если это за вас сможет сделать вывод типов. Единственное исключение может возникнуть при необходимости повышения семантики кода, что относительно аннотаций типа бывает сравнительно редко.
Вывод примитивных типов¶
Вывод типов для значений, принадлежащих к так называемым примитивным типам, не таит в себе ничего необычного. Кроме того, будь это переменные, поля, параметры, возвращаемые из функций и методов, или значения - результат во всех случаях будет идентичным.
1 2 3 4 5 6 7 8 9 |
|
Вывод примитивных типов для констант (const) и полей только для чтения (readonly)¶
Когда дело доходит до присваивания значений, принадлежащих к примитивным типам, таким конструкциям, как константы (const
) и неизменяемые поля (модификатор readonly
), поведение вывода типов изменяется.
В случае, когда значение принадлежит к примитивным типам number
, string
или boolean
, вывод типов указывает принадлежность к литеральным примитивным типам, определяемым самим значением.
1 2 3 4 5 6 7 8 9 |
|
Если значение принадлежит к типу enum
, то вывод типов установит принадлежность к указанному значению.
1 2 3 4 5 6 7 8 9 |
|
Когда вывод типов встречает значение, принадлежащее к типу symbol
, его поведение зависит от конструкции, которой присваивается значение. Так, если вывод типов работает с константой, то тип определяется как запрос типа (глава “Type Queries (запросы типа), Alias (псевдонимы типа)”) самой константы. Если вывод типов устанавливает принадлежность к типу неизменяемого поля, то тип будет определен как symbol
. Происходит так потому, что вместе с созданием нового экземпляра в системе будет определяться и новый символ, что противоречит правилам, установленным для Unique Symbol
(глава “Типы - Примитивные литеральные типы Number, String, Boolean, Unique Symbol, Enum”).
1 2 3 4 5 |
|
Вывод объектных типов¶
С выводом объектных типов не связано ничего необычного. Кроме того, поведение вывода типов одинаково для всех конструкций.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Вывод типа для полей класса на основе инициализации их в конструкторе¶
Если прочитать главу, посвященную конфигурации компилятора, станет известно, что при активном флаге --noImplicitAny
возникает ошибка, если тело класса включает поля без аннотации типа. Дело в том, что вывод типов расценивает поля без явной аннотации типа как принадлежащие к any
, который как раз и не допускает активированный флаг --noImplicitAny
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Но, к счастью, тип полей без явной аннотации может быть автоматически выведен, если инициализация таких полей происходит в конструкторе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Не будет лишним сделать акцент на словах об инициализации в конструкторе, поскольку это условие является обязательным. При попытке инициализации полей вне тела конструктора будет вызвана ошибка, даже если инициализация производится в методе, вызываемом из конструктора.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Если инициализация полей класса без аннотации по каким-то причинам может не состояться, то тип будет выведен как объединение, включающее также и тип undefined
.
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 |
|
Вывод объединенных (Union) типов¶
С выводом типов объединения (глава “Типы - Union, Intersection”) связаны как очевидные, так и нет, случаи.
К очевидным случаям можно отнести массив, состоящий из разных примитивных типов. В этом случае будет выведен очевидный тип объединение, которое определяется типами присутствующих в массиве примитивов.
1 |
|
В случае получения любого элемента массива, вывод типов также установит принадлежность к объединенному типу.
1 2 3 |
|
Неочевидные особенности лучше всего начать с примера, в котором вывод типа определяет принадлежность значения к массиву, состоящему из обычных объектных типов.
1 2 3 4 |
|
В примере вывод типов выводит ожидаемый и предсказуемый результат для массива объектов, чьи типы полностью идентичны. Идентичны они по той причине, что вывод типов установит тип { a: number, b: string }
для всех элементов массива.
Но стоит изменить условие, допустим, убрать объявление одного поля и картина кардинально изменится. Вместо массива обычных объектов тип будет выведен как массив объединенного типа.
1 |
|
Как видно из примера выше, вывод типов приводит все объектные типы, составляющие тип объединение, к единому виду. Он добавляет к типам несуществующие в них, но существующие в других объектных типах поля, декларируя их как необязательные (глава “Операторы - Optional, Not-Null, Not-Undefined, Definite Assignment Assertion”). Сделано это для возможности конкретизировать тип любого элемента массива. Простыми словами, чтобы не получить ошибку во время выполнения, любой элемент массива должен иметь общие для всех элементов признаки. Но так как в реальности в объектах некоторые члены вовсе могут отсутствовать, вывод типов, чтобы повысить типобезопасность, декларирует их как необязательные. Таким образом он предупреждает разработчика о возможности возникновения ситуации, при которой эти члены будут иметь значение undefined
, что и демонстрируется в примере ниже.
1 2 3 4 |
|
Если в качестве значений элементов массива выступают экземпляры классов, не связанных отношением наследования, то они и будут определять тип объединение.
1 2 3 4 5 6 7 8 9 10 |
|
В случае, если элементы массива являются экземплярами классов, связанных отношением наследования (номинативная типизация “Экскурс в типизацию - Совместимость типов на основе вида типизации”), то выводимый тип будет ограничен самым базовым типом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Те же самые правила применяются при выводе типа значения, возвращаемого тернарным оператором.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Так как выражение, расположенное в блоке тернарного оператора, вычисляется на этапе выполнения программы, вывод типов не может знать результата его вычисления на этапе компиляции. Поэтому, чтобы не нарушить типобезопасность, он вынужден указывать объединенный тип, определяемый всеми блоками выражения.
Вывод пересечения (Intersection) с дискриминантными полями¶
Если при определении типа пересечения определяющее его множество включает больше одного типа, определяющего одноименные дискриминантные поля, принадлежащие к разным типам, то такое пересечение определяется как тип never
. Данная тема подробно была рассмотрена в главе "Типы - Discriminated Union".
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 |
|
Но стоит обратить внимание, что речь идет только об одноименных полях, принадлежащих к разным типам. То есть, если множество, определяющее пересечение, включает несколько типов с одноименными дискриминантными полями, принадлежащими к одному типу, то такое множество будет определено ожидаемым образом.
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 |
|
Вывод типов кортеж (Tuple)¶
Начать стоит с напоминания, что значение длины кортежа, содержащего элементы, помеченные как необязательные, принадлежит к типу объединению (Union
), который составляют литеральные числовые типы.
1 2 3 4 5 6 7 |
|
Кроме того, остаточные параметры (...rest
), аннотированные с помощью параметра типа, рассматриваются и представляются выводом типа как принадлежащие к типу-кортежу.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Если функция, определяющая остаточные параметры, принадлежащие к параметру типа, будет вызвана с аргументами, включающими массив, указанный при помощи расширяющего синтаксиса (spread syntax), то тип для него будет выведен в виде остаточного типа ...rest
.
1 2 3 4 5 6 7 8 |
|