Объектные типы
В JS
обычным способом группировки и передачи данных являются объекты. В TS
они представлены объектными типами (object types).
Как мы видели ранее, они могут быть анонимными:
или именоваться с помощью интерфейсов (interfaces):
или синонимов типа (type aliases):
Во всех приведенных примерах наша функция принимает объект, который содержит свойство name
(значение которого должно быть типа string
) и age
(значение которого должно быть типа number
).
#
Модификаторы свойств (property modifiers)Каждое свойство в объектном типе может определять несколько вещей: сам тип, то, является ли свойство опциональным, и может ли оно изменяться.
#
Опциональные свойстваСвойства могут быть помечены как опциональные (необязательные) путем добавления вопросительного знака (?
) после их названий:
Все вызовы функции в приведенном примере являются валидными. Опциональность означает, что если свойство установлено, оно должно иметь указанный тип.
Мы можем получать значения таких свойств. Однако, при включенной настройке strictNullChecks
, мы будем получать сообщения о том, что потенциальными значениями опциональных свойств является undefined
:
В JS
при доступе к несуществующему свойству возвращается undefined
. Добавим обработку этого значения:
Теперь все в порядке. Но для определения "дефолтных" значений (значений по умолчанию) параметров в JS
существует специальный синтаксис:
В данном случае мы деструктурировали параметр painShape
и указали значения по умолчанию для xPos
и yPos
. Теперь они присутствуют в теле функции painShape
, но являются опциональными при ее вызове.
Обратите внимание
В настоящее время не существует способа поместить аннотацию типа в деструктуризацию, поскольку такой синтаксис будет интерпретирован JS
иначе.
shape: Shape
означает "возьми значение свойства shape
и присвой его локальной переменной Shape
". Аналогично xPos: number
создает переменную number
, значение которой основано на параметре xPos
.
#
Свойства, доступные только для чтения (readonly properties)Свойства могут быть помечены как доступные только для чтения с помощью ключевого слова readonly
. Такие свойства не могут перезаписываться в процессе проверки типов:
Использование модификатора readonly
не делает саму переменную иммутабельной (неизменяемой), это лишь запрещает присваивать ей другие значения:
readonly
сообщает TS
, как должны использоваться объекты. При определении совместимости двух типов TS
не проверяет, являются ли какие-либо свойства доступными только для чтения. Поэтому такие свойства можно изменять с помощью синонимов:
#
Сигнатуры индекса (index signatures)Иногда мы не знаем названий всех свойств типа, но знаем форму значений.
В таких случаях мы можем использовать индексы для описания типов возможных значений, например:
В приведенном примере у нас имеется интерфейс StringArray
, содержащий сигнатуру индекса. Данная сигнатура указывает на то, что при индексации StringArray
с помощью number
возвращается string
.
Сигнатура индекса типа свойства должна быть строкой или числом.
Несмотря на поддержку обоих типов индексаторов (indexers), тип, возвращаемый из числового индексатора, должен быть подтипом типа, возвращаемого строковым индексатором. Это объясняется тем, что при индексации с помощью number
, JS
преобразует его в string
перед индексацией объекта. Это означает, что индексация с помощью 100
(number
) эквивалента индексации с помощью "100"
(string
), поэтому они должны быть согласованными между собой.
В то время, как сигнатуры строкового индекса являются хорошим способом для описания паттерна "словарь", они предопределяют совпадение всех свойств их возвращаемым типам. Это объясняется тем, что строковый индекс определяет возможность доступа к obj.property
с помощью obj['property']
. В следующем примере тип name
не совпадает с типом строкового индекса, поэтому во время проверки возникает ошибка:
Тем не менее, свойства с разными типами являются валидными в случае, когда сигнатура индекса - это объединение типов (union):
Сигнатуры индекса можно сделать доступными только для чтения для предотвращения их перезаписи:
#
Расширение типов (extending types)Что если мы хотим определить тип, который является более конкретной версией другого типа? Например, у нас может быть тип BasicAddress
, описывающий поля, необходимые для отправки писем и посылок в США:
В некоторых случаях этого будет достаточно, однако адреса часто имеют литералы. Для таких случаев мы можем определить AddressWithUnit
:
Неужели не существует более простого способа добавления дополнительных полей? На самом деле, мы можем просто расширить BasicAddress
, добавив к нему новые поля, которые являются уникальными для AddressWithUnit
:
Ключевое слово extends
позволяет копировать членов именованных типов в другие типы. Оно также указывает на связь между типами.
Интерфейсы также могут расширяться с помощью нескольких типов одновременно:
#
Пересечение типов (intersection types)interface
позволяет создавать новые типы на основе других посредством их расширения. TS
также предоставляет другую конструкцию, которая называется пересечением типов или пересекающимися типами и позволяет комбинировать существующие объектные типы. Пересечение типов определяется с помощью оператора &
:
Пересечение типов Colorful
и Circle
приводит к возникновению типа, включающего все поля Colorful
и Circle
:
#
Интерфейс или пересечение типов?И интерфейсы, и пересечения типов используются для создания новых типов на основе существующих за счет комбинирования последних. Основное отличие между ними заключается в том, как обрабатываются возникающие конфликты.
#
Общие объектные типыПредположим, что у нас имеется тип Box
, который может содержать любое значение:
Этот код работает, но тип any
является небезопасным с точки зрения системы типов. Вместо него мы могли бы использовать unknown
, но это будет означать необходимость выполнения предварительных проверок и подверженных ошибкам утверждений типов (type assertions).
Более безопасным способом будет определение различных типов Box
для каждого типа contents
:
Однако, это обуславливает необходимость создания различных функций или перегрузок функции (function overloads) для работы с такими типами:
Слишком много шаблонного кода. Более того, в будущем нам может потребоваться определить новый тип и перегрузку. Так не пойдет.
Для решения данной проблемы мы можем создать общий (generic) тип Box
, в котором объявляется параметр типа (type parameter):
Затем, при ссылке на Box
, мы должны определить аргумент типа (type argument) вместо Type
:
По сути, Box
- это шаблон для настоящего типа, в котором Type
будет заменен на конкретный тип. Когда TS
видит Box<string>
, он заменяет все вхождения Type
в Box<Type>
на string
и заканчивает свою работу чем-то вроде { contents: string }
. Другими словами, Box<string>
работает также, как рассмотренный ранее StringBox
.
Тип Box
теперь является переиспользуемым (т.е. имеется возможность использовать этот тип несколько раз без необходимости его модификации). Это означает, что когда нам потребуется коробка (Box
- коробка, контейнер) нового типа, нам не придется определять новый тип Box
:
Это также означает, что нам не нужны перегрузки функции. Вместо них мы можем использовать общую функцию (generic function):
Синонимы типов также могут быть общими. Вот как мы можем определить общий тип (generic type) Box
:
Поскольку синонимы, в отличие от интерфейсов, могут использоваться для описания любых типов, а не только типов объектов, мы можем использовать их следующим образом:
Array
#
Тип Синтаксис number[]
или string[]
- это сокращения для Array<number>
и Array<string>
, соответственно:
Array
сам по себе является общим типом:
Современный JS
также предоставляет другие общие структуры данных, такие как Map<K, V>
, Set<T>
и Promise<T>
. Указанные структуры могут работать с любым набором типов.
ReadonlyArray
#
Тип ReadonlyArray
- это специальный тип, описывающий массив, который не должен изменяться.
Когда мы создаем функцию, которая возвращает ReadonlyArray
, это означает, что мы не собираемся изменять такой массив, а когда мы видим функцию, принимающую ReadonlyArray
, это означает, что мы можем передавать такой функции любой массив и не беспокоиться о том, что он может измениться.
В отличие от Array
, ReadonlyArray
не может использоваться как конструктор:
Однако, мы можем присваивать массиву, доступному только для чтения, обычные массивы:
Для определения массива, доступного только для чтения, также существует сокращенный синтаксис, который выглядит как readonly Type[]
:
В отличие от модификатора свойств readonly
, присваивание между Array
и ReadonlyArray
является однонаправленным (т.е. только обычный массив может быть присвоен доступному только для чтения массиву):
#
Кортеж (tuple)Кортеж - это еще одна разновидность типа Array
с фиксированным количеством элементов определенных типов.
StrNumPair
- это кортеж string
и number
. StrNumPair
описывает массив, первый элемент которого (элемент под индексом 0
) имеет тип string
, а второй (элемент под индексом 1
) - number
.
Если мы попытаемся получить элемент по индексу, превосходящему количество элементов, то получим ошибку:
Кортежи можно деструктурировать:
Рассматриваемый кортеж является эквивалентом такой версии типа Array
:
Элементы кортежа могут быть опциональными (?
). Такие элементы указываются в самом конце и влияют на тип свойства length
:
Кортежи также могут содержать оставшиеся элементы (т.е. элементы, оставшиеся не использованными, rest elements), которые должны быть массивом или кортежем:
...boolean[]
означает любое количество элементов типа boolean
.
Такие кортежи не имеют определенной длины (length
) - они имеют лишь набор известных элементов на конкретных позициях:
Кортежи сами могут использоваться в качестве оставшихся параметров и аргументов. Например, такой код:
является эквивалентом следующего:
#
Кортежи, доступные только для чтения (readonly tuple types)Кортежи, доступные только для чтения, также определяются с помощью модификатора readonly
:
Попытка перезаписи элемента такого кортежа приведет к ошибке:
Кортежи предназначены для определения типов иммутабельных массивов, так что хорошей практикой считается делать их доступными только для чтения. Следует отметить, что предполагаемым типом массива с утверждением const
является readonly
кортеж:
В приведенном примере distanceFromOrigin
не изменяет элементы переданного массива, но ожидает получения изменяемого кортежа. Поскольку предполагаемым типом point
является readonly [3, 4]
, он несовместим с [number, number]
, поскольку такой тип не может гарантировать иммутабельности элементов point
.