Menu

Кортежи (tuple) в TypeScript: именованные и опциональные

Кортеж в TypeScript это массив с фиксированным числом элементов, типы которых известны по позиции, например [string, number]. Синтаксис, именованные, опциональные и rest-элементы, readonly-кортежи и as const, возврат кортежа из функции и отличие кортежей от массивов.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Кортеж в TypeScript это массив с фиксированным числом элементов, в котором у каждой позиции свой тип. [string, number] означает ровно два элемента: сначала строка, затем число. Типы пишутся в квадратных скобках в том порядке, в котором идут значения.

Во время выполнения кортеж это обычный массив JavaScript. Всё, что добавляет кортеж (фиксированная длина и тип на каждой позиции), проверяется компилятором и стирается.

Синтаксис кортежей

Тип кортежаПринимаетТип length
[string, number]ровно строку, затем число2
[x: number, y: number]то же, с метками для читаемости2
[number, number, number?]2 или 3 числа2 | 3
[string, ...number[]]строку, затем любое количество чиселnumber
[...string[], number]любое количество строк, затем числоnumber
readonly [number, number]пару, которую нельзя изменить2
[]только пустой массив0

Каждая форма разобрана ниже. Стоит обратить внимание на тип length: у фиксированного кортежа это литеральный тип, поэтому компилятор знает, что pair.length равно ровно 2.

Что проверяет компилятор

Тип кортежа фиксирует количество элементов, их порядок и тип на каждой позиции. Ошибка в любом из них даёт ошибку компиляции:

index.ts(2,7): error TS2322: Type '[string]' is not assignable to type '[string, number]'.
  Source has 1 element(s) but target requires 2.
index.ts(3,36): error TS2322: Type 'number' is not assignable to type 'string'.
index.ts(3,40): error TS2322: Type 'string' is not assignable to type 'number'.
index.ts(5,16): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.

Обычный массив последнюю ошибку никогда бы не поймал: для string[] выражение arr[2] просто имеет тип string, а во время выполнения оказывается undefined.

Именованные элементы кортежа

Метки описывают, что означает каждая позиция. Они никак не меняют тип и способ индексации, но редакторы показывают их во всплывающих подсказках и подсказках сигнатур, и [number, number] становится гораздо понятнее.

Начиная с TypeScript 5.2 можно пометить одни позиции и оставить другие без меток, как в [first: string, number]. Метки нужны только читателям: [x: number, y: number] и [number, number] это один и тот же тип, и они присваиваются друг другу.

Опциональные элементы

? после типа элемента делает позицию опциональной. Опциональные элементы должны идти после обязательных, и каждый из них расширяет тип length.

Чтение опционального элемента даёт T | undefined, поэтому перед арифметикой нужно значение по умолчанию в шаблоне деструктуризации (a = 1) или проверка.

Rest-элементы

Rest-элемент, ...T[], обозначает любое количество элементов типа T. Он может стоять в конце, в начале или в середине, но в кортеже может быть только один.

length кортежа с rest-элементом имеет тип number, потому что размер больше не фиксирован. Фиксированным остаётся расположение типизированных позиций.

Readonly-кортежи и as const

readonly [T, U] убирает push, pop, splice и присвоение по индексу, то есть делает то, чем и должно быть значение фиксированной длины. as const после литерала массива выводит readonly-кортеж литеральных типов.

(typeof SIZES)[number] превращает кортеж в объединение типов его элементов; этот приём описан на странице индексированные типы доступа. Readonly-кортеж нельзя передать в параметр с типом изменяемого кортежа, поэтому функции, которые только читают, должны принимать readonly [number, number].

Проверка readonly работает только во время компиляции. Во время выполнения массив не заморожен (присвоение выше действительно выполнилось, это видно в выводе), поэтому, если нужна гарантия во время выполнения, используйте Object.freeze.

Возврат кортежа из функции

Именно так, кортежем из нескольких значений, работает useState в React (const [value, setValue] = useState(0)). Подвох в том, что литерал массива в return выводится как массив, а не как кортеж.

index.ts(9,13): error TS2365: Operator '+' cannot be applied to types 'number | (() => number)' and 'number'.
index.ts(10,1): error TS2349: This expression is not callable.
  Not all constituents of type 'number | (() => number)' are callable.
    Type 'number' has no call signatures.

Функция возвращает (number | (() => number))[], поэтому оба имени после деструктуризации получают тип-объединение. Исправлений два: аннотировать возвращаемый тип или добавить as const.

Кортеж в качестве возвращаемого значения позволяет вызывающему коду назвать части как угодно. Если значений больше двух-трёх или порядок неочевиден, возвращайте объект: { count, increment } документирует себя сам.

Кортежи как параметры функций

Rest-параметр с типом кортежа описывает весь список аргументов, включая опциональные. Именно так встроенный служебный тип Parameters<T> представляет параметры функции.

Разворачивание кортежа в вызов проверяет каждый аргумент по позиции, чего разворачивание (string | number)[] сделать не может.

Кортеж и массив

Массив (string | number)[]Кортеж [string, number]
Длиналюбаяфиксированная (или ограниченная опциональными и rest-элементами)
Тип x[0]string | numberstring
Тип x[5]string | numberошибка компиляции TS2493
Тип lengthnumber2
Порядок типовне отслеживаетсяотслеживается
Значение во время выполнениямассив JavaScriptтот же массив JavaScript
Типичное применениесписки однородных элементовнебольшие фиксированные группы: пары, координаты, [key, value], несколько возвращаемых значений

Кортежи встречаются и во встроенных типах. Object.entries(obj) возвращает [string, T][], а Map создаётся из кортежей [key, value]:

Одна ловушка: у изменяемого кортежа есть все методы массива, поэтому pair.push(3) компилируется на [string, number] и незаметно создаёт массив из трёх элементов, хотя тип говорит о двух. Объявление кортежей как readonly закрывает эту дыру. И раз типы стираются, данные извне программы (JSON, API) во время выполнения по типу кортежа не проверяются: прежде чем им доверять, проверьте длину и типы элементов.

Вариативные типы кортежей

Типы кортежей могут разворачивать другие типы кортежей, [...T, ...U]. Вместе с дженериками это позволяет типизировать функции, которые склеивают кортежи или добавляют элементы в начало, сохраняя каждую позицию:

Типы библиотек тоже опираются на вывод кортежей: Promise.all([fetchUser(), fetchPosts()]) разрешается в кортеж с отдельным типом для каждого входного промиса.

Часто задаваемые вопросы

Что такое кортеж в TypeScript?

Кортеж это тип массива фиксированной длины, в котором у каждой позиции свой тип: [string, number] это ровно два элемента, сначала строка, затем число. Во время выполнения это обычный массив JavaScript; длина и типы по позициям проверяются только во время компиляции.

Чем кортеж отличается от массива в TypeScript?

У типа массива вроде (string | number)[] любая длина, и у каждого элемента один и тот же тип (объединение), поэтому arr[0] имеет тип string | number. У кортежа вроде [string, number] длина известна, t[0] это string, t[1] это number, а t[2] даёт ошибку компиляции.

Как вернуть кортеж из функции в TypeScript?

Аннотируйте возвращаемый тип, function f(): [number, string], или завершите возвращаемое выражение as const, что даст readonly-кортеж. Без этого return [count, setCount] выводится как массив объединения, например (number | (() => void))[], и деструктуризация даёт типы-объединения.

Что такое именованные элементы кортежа?

Это метки на позициях, [name: string, age: number]. Они не меняют ни тип, ни способ доступа (по-прежнему t[0]), но редакторы показывают их во всплывающих подсказках и в подсказках параметров функций, параметры которых типизированы кортежем. Опциональные и rest-элементы работают с метками: [x: number, y?: number], [head: string, ...rest: number[]].

Можно ли делать push в кортеж в TypeScript?

В изменяемый кортеж можно: push компилируется, потому что кортежи наследуют методы массивов, хотя это нарушает фиксированную длину. Объявите кортеж как readonly (или создайте его через as const), и push, pop и присвоение по индексу станут ошибками компиляции.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ