Конструкции языка
Приведение типов
До этого момента мы ни разу не говорили о том, как изменять тип значения. В TypeScript есть несколько способов изменения типа. Один из них - это приведение типа или каст (cast). Каст изменяет тип значения на тот, который был указан. Существует два синтаксиса для каста - с использованием <>
и as
.
Каст через <>
и через as
- это эквивалентные действия. Однако каст через <>
не будет работать в .tsx
файлах (это как .jsx
файлы, только на TypeScript), потому что такой каст будет интерпретироваться как компонент React. Поэтому предпочтительней использовать as
каст.
По своей сути каст - это ручное преобразование одного типа в другой. Это задача возникает например тогда, когда нужно более общий тип преобразовать к более конкретному: из number
получить числовой литерал, от базового класса перейти к конкретному наследнику и так далее.
Каст может создавать проблемы. TypeScript следит за тем, чтобы преобразование одного типа осуществлялось в совместимый тип. Например, нельзя преобразовать number
в string
.
Однако это ограничение можно обойти, сделав сначала каст до any
, а потом до нужного типа.
Иногда преобразование в несовместимый тип приходится делать. Но кастом через any
не стоит злоупотреблять. Использование подобной конструкции может вызвать проблемы в runtime. После приведения типов, TypeScript использует для компиляции приведенный тип. Однако значение, тип которого был изменен (особенно через as any as
) может быть не совместимо с операциями, которые можно делать с приведенным типом. Например, не иметь каких то методов. В таком случаи ошибка возникнет в runtime, а не на этапе компиляции.
Для того, чтобы сломать runtime при помощи каста, необязательно преобразовывать несовместимые типы. Можно выполнять преобразование совместимых типов и все равно получить ошибку в runtime.
Type guards
В TypeScript есть другие способы преобразования типов. Один из таких способов - это использование type guard-ов. Рассмотрим пример:
В коде выше объявлено интерфейсы клавиатуры и мыши. Также объявлена функция, которая принимает объект совместимый с типом клавиатуры или типом мыши. Далее нужно написать код, который проверит, какой объект был передан в функцию и вызовет соответствующий метод.
Выражение "click" in pointer
является type guard-ом. Оно в runtime проверяет, что поле "click"
содержится внутри объекта pointer
. Если это выражение возвращает true
, то внутри блока if
гарантируется, что "click"
в объекте присутствует, а значит и с точки зрения типов у pointer
должно быть поле click
. Гарантируется также, что в блоке else
у объекта pointer
нет поля click
.
На самом деле в коде выше описан особый случай для type guard-а in
. pointer
имеет тип Mouse | Keyboard
, а оба интерфейса имеет разные названия полей. Соответственно, внутри блока if
, TypeScript не просто убедился, что у pointer
есть click
. Он также определил, что click
есть только у Mouse
, а значит значения типа Keyboard
не могут попасть в блок if
, поэтому pointer
внутри блока if
имеет тип Mouse
. Верно и обратное утверждение. Если "click" in pointer
возвращает false
, то в блоке else
объект pointer
не может иметь тип Mouse
, потому что для этого у него должно быть поле click
. Поэтому в блоке else
у pointer
тип Keyboard
.
Операторы ===
и !==
в TypeScript тоже являются type guard-ами.
При помощи ===
можно определять одно значение из множества других значений, а также исключать это значения из множества при помощи !==
или блока else
.
При помощи ===
/!==
и оператора typeof
можно определять тип значения, если изначально тип этого значения состоял из объединения примитивов.
Оператор instanceof
также является type guard-ом.
В TypeScript можно определить функции, которые являются tpye guard-ами. Для этого используется синтаксическая конструкция {paramter} is {type}
В коде выше объявлена функция isNumber
возвращаемое значение которой a is number
. По сути это boolean
, но здесь также указано, что если возвращаемое значение true
, то значение в параметра a
- это number
. Таким образом, функция isNumber
становится type guard-ом.
В TypeScript уже есть встроенные функции type guard-ы, например Array.isArray
.
Pattern Matching
Pattern Matching еще один подход, который позволяет преобразовать один тип во второй. Допустим, есть такой код:
Тип поля q.kind
TypeScript выводит как "a" | "b"
. Можно воспользоваться оператором ===
, для того, чтобы преобразовать тип A | B
в A
или B
.
В такой ситуации можно воспользоваться оператором switch
по полю q.kind
:
В этом случае TypeScript также выводит тип внутри блоков case
. Этот подход называется pattern matching.
Enum
Enum (enumeration - перечисление) - это еще одна синтаксическая конструкция TypeScript, которая пригождается в реальных задачах. Она не связан с преобразованием типов. Ее использования чаще всего связаны с взаимодействием с сервером.
Во многих языках программирования, на которых разрабатываются сервера, есть возможность объявить специальную структуру данных, которая хранит в себе именованный набор констант. Например, если мы разрабатываем какое-нибудь консольное приложение для отрисовки 2D графики в черно-белом формате, то для определения цвета можно использовать булевой флаг isBlack
. Но когда цветов становится больше чем 2, использовать булевой флаг не получится. Можно использовать числовые значения от 0 до n. Пример сигнатуры функции, которая рисует пиксель по заданным координатам, на TypeScript может выглядеть так:
У такого подхода есть несколько проблема. Например, если поддерживается всего 4 цвета: 0, 1, 2 и 3, то в данном случаи типизация позволяет передавать любое число в качестве параметра color
. Это может привести к проблемам в runtime. Конкретно в TypeScript для решения этой проблемы можно использовать специальный тип - объединение числовых литералов 0 | 1 | 2 | 3
. Но очень многие языки программирования не поддерживают литеральные типы. Более того, при таком подходе становится сложно читать код:
В качестве цвета можно использовать его название в виде строки. Но тогда не решается проблема с передачей не валидных данных.
В разных языках программирования (в частности в TypeScript) есть enum-ы или подобные структуры данных. В TypeScript он выглядит так:
Enum в TypeScript - это объект, ключами которого являются строчные значения, указанные в enum-е, а значениями - числа или строки. Для кода выше TypeScript при компиляции сгенерирует следующий JavaScript:
Если ключам enum-а явно не передавать значения, то им присвоятся значения начиная с 0 сверху вниз увеличиваясь на 1. Любому элементу можно задать значение явно. Тогда у следующего элемента значение будет увеличено на 1 от предыдущего (если оно явно не указано).
Значения enum-а могут быть и строки. Но в таком случаи у всех ключей enum-а значения должны быть указаны явно, иначе будет ошибка компиляции. В одном enum-е могут быть одновременно и строчные и числовые значения.
Задания
Last updated
Was this helpful?