Типы на каждый день
string
, number
и boolean
#
Примитивы: В JS
часто используется 3 примитива: string
, number
и boolean
. Каждый из них имеет соответствующий тип в TS
:
string
представляет строковые значения, например,'Hello World'
number
предназначен для чисел, например,42
.JS
не различает целые числа и числа с плавающей точкой (или запятой), поэтому не существует таких типов, какint
илиfloat
- толькоnumber
boolean
- предназначен для двух значений:true
иfalse
Обратите внимание
Типы String
, Number
и Boolean
(начинающиеся с большой буквы) являются легальными и ссылаются на специальные встроенные типы, которые, однако, редко используются в коде. Для типов всегда следует использовать string
, number
или boolean
.
#
МассивыДля определения типа массива [1, 2, 3]
можно использовать синтаксис number[]
; такой синтаксис подходит для любого типа (например, string[]
- это массив строк и т.д.). Также можно встретить Array<number>
, что означает тоже самое. Такой синтаксис, обычно, используется для определения общих типов или дженериков (generics).
Обратите внимание
[number]
- это другой тип, кортеж (tuple).
any
#
TS
предоставляет специальный тип any
, который может использоваться для отключения проверки типов:
Тип any
может быть полезен в случае, когда мы не хотим писать длинное определение типов лишь для того, чтобы пройти проверку.
noImplicitAny
#
При отсутствии определения типа и когда TS
не может предположить его на основании контекста, неявным типом значение становится any
.
Обычно, мы хотим этого избежать, поскольку any
является небезопасным с точки зрения системы типов. Установка флага noImplicitAny
позволяет квалифицировать любое неявное any
как ошибку.
#
Аннотации типа для переменныхПри объявлении переменной с помощью const
, let
или var
опционально можно определить ее тип:
Однако, в большинстве случаев этого делать не требуется, поскольку TS
пытается автоматически определить тип переменной на основе типа ее инициализатора, т.е. значения:
#
ФункцииВ JS
функции, в основном, используются для работы с данными. TS
позволяет определять типы как для входных (input), так и для выходных (output) значений функции.
#
Аннотации типа параметровПри определении функции можно указать, какие типы параметров она принимает:
Вот что произойдет при попытке вызвать функцию с неправильным аргументом:
Обратите внимание
Количество передаваемых аргументов будет проверяться даже при отсутствии аннотаций типа параметров.
#
Аннотация типа возвращаемого значенияТакже можно аннотировать тип возвращаемого функцией значения:
Как и в случае с аннотированием переменных, в большинстве случаев TS
может автоматически определить тип возвращаемого функцией значения на основе инструкции return
.
#
Анонимные функцииАнонимные функции немного отличаются от обычных. Когда функция появляется в месте, где TS
может определить способ ее вызова, типы параметров такой функции определяются автоматически.
Вот пример:
Несмотря на отсутствие аннотации типа для s
, TS
использует типы функции forEach
, а также предполагаемый тип массива для определения типа s
. Этот процесс называется определением типа на основе контекста (contextual typing).
#
Типы объектаОбъектный тип - это любое значение со свойствами. Для его определения мы просто перечисляем все свойства объекта и их типы. Например, так можно определить функцию, принимающую объект с координатами:
Для разделения свойств можно использовать ,
или ;
. Тип свойства является опциональным. Свойство без явно определенного типа будет иметь тип any
.
#
Опциональные свойстваДля определения свойства в качестве опционального используется символ ?
после названия свойства:
В JS
при доступе к несуществующему свойству возвращается undefined
. По этой причине, при чтении опционального свойства необходимо выполнять проверку на undefined
:
#
Объединения (unions)Обратите внимание
В литературе, посвященной TS
, union
, обычно, переводится как объединение, но фактически речь идет об альтернативных типах, объединенных в один тип.
#
Определение объединенияОбъединение - это тип, сформированный из 2 и более типов, представляющий значение, которое может иметь один из этих типов. Типы, входящие в объединение, называются членами (members) объединения.
Реализуем функцию, которая может оперировать строками или числами:
#
Работа с объединениямиВ случае с объединениями, TS
позволяет делать только такие вещи, которые являются валидными для каждого члена объединения. Например, если у нас имеется объединение string | number
, мы не сможем использовать методы, которые доступны только для string
:
Решение данной проблемы заключается в сужении (narrowing) объединения. Например, TS
знает, что только для string
оператор typeof
возвращает 'string'
:
Другой способ заключается в использовании функции, такой как Array.isArray
:
В некоторых случаях все члены объединения будут иметь общие методы. Например, и массивы, и строки имеют метод slice
. Если каждый член объединения имеет общее свойство, необходимость в сужении отсутствует:
#
Синонимы типов (type aliases)Что если мы хотим использовать один и тот же тип в нескольких местах? Для этого используются синонимы типов:
Синонимы можно использовать не только для объектных типов, но и для любых других типов, например, для объединений:
Обратите внимание
Синонимы - это всего лишь синонимы, мы не можем создавать на их основе другие "версии" типов. Например, такой код может выглядеть неправильным, но TS
не видит в нем проблем, поскольку оба типа являются синонимами одного и того же типа:
#
Интерфейсы (interfaces)Определение интерфейса - это другой способ определения типа объекта:
TS
иногда называют структурно типизированной системой типов (structurally typed type system) - TS
заботит лишь соблюдение структуры значения, передаваемого в функцию printCoords
, т.е. содержит ли данное значение ожидаемые свойства.
#
Разница между синонимами типов и интерфейсамиСинонимы типов и интерфейсы очень похожи. Почти все возможности interface
доступны в type
. Ключевым отличием между ними является то, что type
не может быть повторно открыт для добавления новых свойств, в то время как interface
всегда может быть расширен.
Пример расширения интерфейса:
Пример расширения типа с помощью пересечения (intersection):
Пример добавления новых полей в существующий интерфейс:
Тип не может быть изменен после создания:
Общее правило: используйте interface
до тех пор, пока вам не понадобятся возможности type
.
#
Утверждение типа (type assertion)В некоторых случаях мы знаем о типе значения больше, чем TS
.
Например, когда мы используем document.getElementById
, TS
знает лишь то, что данный метод возвращает какой-то HTMLElement
, но мы знаем, например, что будет возвращен HTMLCanvasElement
. В этой ситуации мы можем использовать утверждение типа для определения более конкретного типа:
Для утверждения типа можно использовать другой синтаксис (е в TSX-файлах):
TS
разрешает утверждения более или менее конкретных версий типа. Это означает, что преобразования типов выполнять нельзя:
Иногда это правило может быть слишком консервативным и мешать выполнению более сложных валидных преобразований. В этом случае можно использовать двойное утверждение: сначала привести тип к any
(или unknown
), затем к нужному типу:
#
Литеральные типы (literal types)В дополнение к общим типам string
и number
, мы можем ссылаться на конкретные строки и числа, находящиеся на определенных позициях.
Вот как TS
создает типы для литералов:
Сами по себе литеральные типы особой ценности не представляют:
Но комбинация литералов с объединениями позволяет создавать более полезные вещи, например, функцию, принимающую только набор известных значений:
Числовые литеральные типы работают похожим образом:
Разумеется, мы можем комбинировать литералы с нелитеральными типами:
#
Предположения типов литераловПри инициализации переменной с помощью объекта, TS
будет исходить из предположения о том, что значения свойств объекта в будущем могут измениться. Например, если мы напишем такой код:
TS
не будет считать присвоение значения 1
полю, которое раньше имело значение 0
, ошибкой. Это объясняется тем, что TS
считает, что типом obj.counter
является number
, а не 0
.
Тоже самое справедливо и в отношении строк:
В приведенном примере предположительный типом req.method
является string
, а не 'GET'
. Поскольку код может быть вычислен между созданием req
и вызовом функции handleRequest
, которая может присвоить req.method
новое значение, например, GUESS
, TS
считает, что данный код содержит ошибку.
Существует 2 способа решить эту проблему.
- Можно утвердить тип на каждой позиции:
- Для преобразования объекта в литерал можно использовать
as const
:
null
и undefined
#
В JS
существует два примитивных значения, сигнализирующих об отсутствии значения: null
и undefined
. TS
имеет соответствующие типы. То, как эти типы обрабатываются, зависит от настройки strictNullChecks
(см. часть 1).
#
Оператор утверждения ненулевого значения (non-null assertion operator)TS
предоставляет специальный синтаксис для удаления null
и undefined
из типа без необходимости выполнения явной проверки. Указание !
после выражения означает, что данное выражение не может быть нулевым, т.е. иметь значение null
или undefined
:
#
Перечисления (enums)Перечисления позволяют описывать значение, которое может быть одной из набора именованных констант. Использовать перечисления не рекомендуется.
#
Редко используемые примитивыbigint
#
Данный примитив используется для представления очень больших целых чисел BigInt
:
Подробнее о BigInt
можно почитать здесь.
symbol
#
Данный примитив используется для создания глобально уникальных ссылок с помощью функции Symbol()
:
Подробнее о символах можно почитать здесь.