Объекты
Тип объекта
Ранее мы рассматривали примитивные типы и массивы. Сейчас мы посмотрим на то, как TypeScript работает с объектами
Какой тип TypeScript присвоит переменной a
? У этой переменной будет тип { a: number }
. Мы можем явно задать переменной этот тип. Это делается по аналогии с другими типами.
Если попытаться присвоить переменной с типом {a: number}
объект без поля a
или с полем a
другого типа - TypeScript выдаст ошибку.
После инициализации объекта в нем нельзя определить поле, которого нет в типе. Также нельзя присвоить полю значения типа, который не совместим с типом поля.
Отметим, что все правила типизации переменных, которые были рассмотрены ранее, применяются и для полей объекта. Иными словами, поле объекта может быть примитивным типом, другим объектом, типом-литералом, объединением или пересечением типов, массивом, кортежем и так далее.
Проверка типов объектов
В TypeScript тип объекта x
совместим с типом объекта y
, если у y
есть все поля с совместимыми типами, что и у x
.
При этом тип y
может иметь поля, которых нет в типе x
.
Важно заметить, что типы полей двух объектов не обязательно должны быть идентичны. Проверка типов совместимости полей работает по тем же правилам, что и присваивание значения переменной.
Type alias
До этого момента все типы были описаны по месту или использовался автовывод типов. Однако, в TypeScript есть возможность объявить тип отдельно и использовать его в нескольких местах.
Такая конструкция называется type alias. Таким образом происходит инициализация своего рода "переменной" в системе типизации. Во время компиляции в JavaScript type alias-ы в результирующий файл не попадут.
В type alias можно сохранять любые типы, например, результаты выполнений операций над типами (объединения, пересечения и т.д.), примитивные типы, массивы, объекты, литералы и так далее.
typeof
Иногда возникает потребность сохранить тип какой-то уже инициализированной переменной и использовать этот тип в другом месте. Для этого в TypeScript есть оператор typeof
.
Очень важно не путать этот оператор с одноименным оператором из JavaScript. TypeScript поддерживает оба оператора. Это возможно потому, что синтаксически они используются в разных местах: один в местах работы с типами, другой - со значениями.
Интерфейс
Существует еще один способ описать тип объекта - использовать интерфейс.
Для этого необходимо использовать ключевое слово interface
, далее указать название типа и без знака =
указать тип объекта. Чаще для указания типа объекта используется именно интерфейс, а не type alias.
Различия между type alias и interface
В type alias можно положить не только объект, а любой тип, включая interface или другой type alias.
При объявлении интерфейсов с одинаковым именем тип объекта расширяется
Если же объявить два type alias с одинаковым именем, будет ошибка
Интерфейсы могут наследоваться друг от друга. Это можно сделать при помощи ключевого слова
extends
. При наследовании интерфейс-потомок имеет все поля интерфейса-родителя и те поля, которые объявлены непосредственно у него самого. Type alias наследоваться не могут.
Наследование позволяет выделить у объектов общие признаки и работать с разными значениями как с общим типом. Например.
Мы описали интерфейс DomElement
, который определяет размеры элемента. У этого интерфейса 2 наследника DivElement
и ButtonElement
. Оба эти наследника имеют все поля базового типа (а именно width
и height
) и какие-то собственные поля.
Не смотря на то, что переменные div
и button
имеют типы DivElement
и ButtonElement
соответственно, их всегда можно интерпретировать как DomElement
.
Однако, как только более конкретный тип (наследник) начинает использоваться как базовый тип, все особенности (поля и методы) конкретного типа теряются (с точки зрения типизация - в runtime все есть).
Это далеко не все отличительные особенности интерфейсов и type alias. Далее будут выявляться все больше отличительных особенностей этих конструкций. Однако, уже сейчас можно сделать несколько выводов:
Интерфейсы позволяют типизировать только объекты. Поэтому поддерживают некоторые специфические особенности типизации объектов, такие как наследование и расширение.
Type alias предназначены для хранения абсолютно любого типа. Из-за этого они не поддерживают специфические особенности типизации объектов, также как и специфические особенности типизации других типов (строк, булевых значений и т.д.). Но так как тип объекта - это тоже тип, а type alias - хранилище для любого типа, отсюда и появляется это двоякая возможность задания типа объекта.
Readonly и опциональные поля
В TypeScript при описании типа объекта можно указать, что поле является не изменяемыми. Делается это при помощи модификатора readonly
.
Значение этих полей указывается во время инициализации объекта. Присвоить новое значение таким полям нельзя - можно только полностью заменить весь объект.
Важно отметить что модификатор readonly
работает только во время проверки типов. После компиляции readonly
(как и все типы) не попадают в результирующие файлы. Поэтому в runtime эти поля можно изменять как угодно.
Часто возникает потребность описать тип объекта, у которого некоторые поля являются опциональными. Для этого в TypeScript можно воспользоваться знаком ?
.
Поле может быть и опциональным, и readonly
одновременно. Такое поле можно не указывать во время инициализации объекта, но нельзя менять далее в коде.
Индексаторы
В TypeScript в типе объекта можно описать индексатор.
Индексатор говорит о том, что у объекта нет конкретных названий полей. Известен лишь тип полей и тип значения поля. Тип полей может быть или string
, или number
. Нельзя использовать литеральные типы или их объединения (по крайней мере в interface-ах и в таком виде). Вместо это нужно объявлять тип с обычными полями.
В одном типе могут быть сразу два индексатора с разным типом полей, но тип значений числового индекса, должен быть подтипом строчного индекса. Это ограничение введено из-за особенностей JavaScript. Когда вы используется числовой индекс [10]
, JavaScript конвертирует значение индекса в строку. Получается, что с точки зрения JavaScript вызов индекса [10]
и ["10"]
- эквивалентны.
Пересечение объектов
В прошлой части мы говорили про операцию пересечения типов (&
). Для простых типов эта операция почти не имеет смысла, но для типов объектов она очень важна. Предположим, у нас есть два тип объекта.
Рассмотрим два этих типа с точки зрения множеств. Тип A
является множеством всех объектов, у которых есть поле a
, значение которого принадлежит множеству number
. Тип B
- множество объектов с полем b
, тип которого string
. Из-за того, что TypeScript использует утиную типизацию, то типу A
может подходить объект, который имеет поле a: number
и еще любое количество других полей с любым типом. Точно также для B
.
Из этого следует, что существует такие объекты, которые одновременно входят во множество A
и во множество B
. Множество этих объектов можно обозначить как пересечение множества A
и B
- A & B
. С точки зрения типизации типу A & B
можно присвоить любой объект, у которого обязательно должны быть поля a: number
и b: string
, и любое количество других полей.
Взятие тип значения ключа. keyof
В TypeScript из типа объекта по названию ключей можно извлекать типы значений этих ключей.
Применяя к типу объекта []
с названием ключа, мы получаем тип значения, который соответствует этому ключу. Если попытаться взять тип значения ключа, которого в объекте нет - TypeScript выдаст ошибку.
Важно, что в []
можно передавать не только один ключ, а объединение названий ключей (например, "a" | "b"
). В результате такого действия мы получим тип, который объединяет типы значений переданных ключей.
В TypeScript есть специальный оператор keyof
. Он возвращает объединение названий ключей типа объекта, к которому его применили.
Так как keyof
возвращает объединение названий, то его результат можно сразу передать в []
, чтобы получить объединение всех типов значений ключей объекта
Или мы хотим вынести тип поля объекта, не имея типа объекта.
Задания
Пример решения задания #4
Last updated
Was this helpful?