Кортежи
Типы кортежей представляют собой список фиксированной длины, элементы которого могут иметь разные типы.
const pair: [string, number] = ["age", 42];
Три формы кортежей в TS не имеют прямого аналога во Flow. (1) Разворачивание кортежа с необязательными элементами: Flow выдаёт ошибку «invalid tuple arity», тогда как TS принимает такое разворачивание и в контекстах с сохранением типа кортежа представляет необязательный слот как T | undefined, сохраняя фиксированными следующие позиции — это небезопасно, если необязательный элемент отсутствует во время выполнения. (2) Необязательные элементы без меток, например [number, string?]: Flow требует форму с меткой, [a: number, b?: string]. (3) readonly [T, S] сокращённая форма не поддерживается во Flow; вместо неё используйте Readonly<[T, S]> (TS поддерживает обе формы).
Когда это использовать
Используйте кортежи вместо массивов, если список содержит фиксированное число элементов, каждая позиция имеет определённое значение и потенциально отличается по типу. Используйте массивы, если длина списка может меняться, а все элементы имеют один и тот же тип.
Основы кортежей
Литералы массивов JavaScript можно использовать для создания типов как кортежей, так и массивов:
const arr: Array<number> = [1, 2, 3]; // As an array type const tup: [number, number, number] = [1, 2, 3]; // As a tuple type
Во Flow типы кортежей можно создавать с помощью синтаксиса [type1, type2, type3]:
const tuple1: [number] = [1]; const tuple2: [number, boolean] = [1, true]; const tuple3: [number, boolean, string] = [1, true, "three"];
При получении значения кортежа по определённому индексу возвращается тип, соответствующий этому индексу:
const tuple: [number, boolean, string] = [1, true, "three"]; const num: number = tuple[0]; // Works! const bool: boolean = tuple[1]; // Works! const str: string = tuple[2]; // Works!
Попытка обратиться к несуществующему индексу приводит к ошибке выхода за границы массива:
const tuple: [number, boolean, string] = [1, true, "three"]; const none = tuple[3]; // Error!
Если Flow не знает, к какому индексу вы обращаетесь, он вернёт все возможные типы:
const tuple: [number, boolean, string] = [1, true, "three"];
function getItem(n: number) {
const val: number | boolean | string = tuple[n];
// ...
} При присваивании нового значения элементу кортежа это значение должно соответствовать типу элемента с данным индексом:
const tuple: [number, boolean, string] = [1, true, "three"]; tuple[0] = 2; // Works! tuple[1] = false; // Works! tuple[2] = "foo"; // Works! tuple[0] = "bar"; // Error! tuple[1] = 42; // Error! tuple[2] = false; // Error!
Строго фиксированная длина кортежа (арность)
Длина кортежа называется «арностью». Во Flow длина кортежа строго фиксирована.
Это означает, что короткий кортеж нельзя использовать вместо более длинного:
const tuple1: [number, boolean] = [1, true]; const tuple2: [number, boolean, void] = tuple1; // Error!
Также длинный кортеж нельзя использовать вместо короткого:
const tuple1: [number, boolean, void] = [1, true, undefined]; const tuple2: [number, boolean] = tuple1; // Error!
Необязательные элементы задают диапазон арности.
Кортежи несовместимы с типами массивов
Поскольку Flow не знает длину массива, тип Array<T> нельзя передать в кортеж:
const array: Array<number> = [1, 2]; const tuple: [number, number] = array; // Error!
Также тип кортежа нельзя передать в тип Array<T>, поскольку тогда кортеж можно было бы небезопасно изменить (например, использовать push для добавления к нему третьего элемента):
const tuple: [number, number] = [1, 2]; const array: Array<number> = tuple; // Error!
Однако его можно передать в тип ReadonlyArray, поскольку изменение запрещено:
const tuple: [number, number] = [1, 2]; const array: ReadonlyArray<number> = tuple; // Works!
Изменяющие массив методы нельзя использовать для кортежей
Для кортежей нельзя использовать методы Array.prototype, которые изменяют кортеж; можно использовать только методы, которые этого не делают:
const tuple: [number, number] = [1, 2];
tuple.join(', '); // Works!
tuple.push(3); // Error! Уточнение по длине
Можно уточнить объединение кортежей по их длине:
type Union = [number, string] | [boolean];
function f(x: Union) {
if (x.length === 2) {
// `x` is `[number, string]`
const n: number = x[0]; // OK
const s: string = x[1]; // OK
} else {
// `x` is `[boolean]`
const b: boolean = x[0];
}
} Метки элементов кортежа
ПРИМЕЧАНИЕ: для этого и следующих разделов необходимо обновить инструменты, как описано в разделе «Внедрение» в конце этой страницы.
Элементам кортежа можно присваивать метки. Метка не влияет на тип элемента кортежа, но помогает документировать назначение элементов, особенно если несколько элементов имеют один и тот же тип.
type Range = [x: number, y: number];
Метка также необходима, чтобы добавить к элементу аннотацию вариантности или модификатор необязательности (без метки возникала бы неоднозначность при разборе).
Аннотации вариантности и кортежи только для чтения
К элементам кортежа с метками, как и к свойствам объектов, можно добавлять аннотации вариантности (указывающие, что элемент доступен только для чтения или только для записи):
type T = [readonly foo: number, writeonly bar: string];
Это позволяет помечать элементы как доступные только для чтения или только для записи. Например:
function f(readOnlyTuple: [readonly foo: number, readonly bar: string]) {
const n: number = readOnlyTuple[0]; // OK to read
readOnlyTuple[1] = 1; // ERROR! Cannot write
} Типы кортежей также можно использовать с Readonly — сокращённой формой, которая помечает каждое свойство как доступное только для чтения:
type T = Readonly<[number, string]>; // Like marking each element readonly
Необязательные элементы кортежа
Элементы кортежа можно пометить как необязательные, добавив ? после метки элемента. Это позволяет не указывать необязательные элементы. Необязательные элементы должны находиться в конце типа кортежа, после всех обязательных элементов.
type T = [foo: number, bar?: string]; [1, "s"] as T; // OK: has all elements [1] as T; // OK: skipping optional element
Нельзя присваивать undefined необязательному элементу — добавьте | void к типу элемента, если хотите это сделать:
type T = [foo?: number, bar?: number | void]; declare const x: T; x[0] = undefined; // ERROR [undefined] as T; // ERROR x[1] = undefined; // OK: we've added `| void` to the element type
Также можно использовать типы-утилиты Partial и Required, чтобы сделать все элементы необязательными или обязательными соответственно:
type AllRequired = [number, string]; [] as Partial<AllRequired>; // OK: like `[a?: number, b?: string]` now type AllOptional = [a?: number, b?: string]; [] as Required<AllOptional>; // ERROR: like `[a: number, b: string]` now
Арность (длина) кортежей с необязательными элементами представляет собой диапазон, а не одно число. Например, длина [number, b?: string] составляет от 1 до 2.
Разворачивание кортежа
Тип кортежа можно развернуть в другой тип кортежа, чтобы получить более длинный кортеж:
type A = [number, string]; type T = [...A, boolean]; // Same as `[number, string, boolean]` [1, "s", true] as T; // OK
При разворачивании кортежей сохраняются метки, вариантность и необязательность. Разворачивать массивы в кортежи нельзя — можно разворачивать только другие кортежи.
Если на уровне значений развернуть кортеж с необязательными элементами в литерале массива, после такого разворачивания нельзя добавлять другие элементы и сохранять для значения представление в виде кортежа. Это связано с тем, что длина кортежа с необязательными элементами задаётся диапазоном, поэтому неизвестно, на каком индексе окажутся последующие значения. При этом значение всё ещё можно типизировать как соответствующий тип Array<T> — затрагивается только представление значения в виде кортежа.
const a: [foo?: 1] = []; const b = [0, ...a, 2]; // At runtime this is `[0, 2]` b as [0, 1 | void, 2]; // ERROR b as ReadonlyArray<number | void>; // OK const c: [0, foo?: 1] = [0]; const d: [bar?: 2] = [2]; const e = [...c, ...d]; // At runtime this is `[0, 2]` e as [0, foo?: 1, bar?: 2]; // ERROR e as ReadonlyArray<number | void>; // OK
Flow сообщает об этом как об ошибке [invalid-tuple-arity] («литерал массива содержит неизвестное число элементов»). Причина в обеспечении безопасности типов: если необязательный элемент отсутствует во время выполнения, следующие позиции сдвигаются влево, и статическая структура кортежа больше не соответствует фактическому расположению элементов.
Неточные кортежи
Неточные типы кортежей работают так же, как неточные объекты: они допускают неизвестные элементы в конце кортежа.
[] as [...]; // OK [1] as [...]; // OK [1] as [number, ...]; // OK
Все кортежи являются подтипами пустого неточного кортежа [...].
Если развернуть неточный кортеж, результат также будет неточным. После разворачивания неточного кортежа нельзя задавать элементы, поскольку неизвестно, на каких индексах они должны находиться.
declare const x: [number, ...]; const y = [0, ...x]; y as [number, number]; // ERROR: it's inexact y as [number, number, ...]; // OK [...x, 2]; // ERROR: can't have element after inexact spread
Неточные кортежи позволяют указать, что параметр обобщённого типа должен быть кортежем. Например:
function mapTupleArray<T extends [...], R>(
tuples: Array<T>, // An array of tuples
f: (...T) => R, // Function args match the tuple's types
): Array<R> {
return tuples.map(args => f(...args));
}
mapTupleArray(
[[1, 'hi'], [3, 'bye']],
(x: number, y: string) => y.length === x,
); // OK
declare const arrays: Array<Array<number>>;
mapTupleArray(arrays, (x: number, y: number) => x + y); // ERROR: array is not a tuple Внедрение
Чтобы использовать элементы кортежа с метками (в том числе необязательные элементы и аннотации вариантности), а также элементы разворачивания кортежей, необходимо обновить инфраструктуру, чтобы она поддерживала этот синтаксис:
-
prettier: 3 -
babelсflow-parser/babel-plugin. Инструкции по настройке см. в руководстве по Babel. -
eslintсflow-eslint. Инструкции по настройке см. в руководстве по ESLint.
Чтобы использовать неточные кортежи, обновите следующие инструменты:
-
prettier: 3.3 -
babelсflow-parser/babel-plugin. Инструкции по настройке см. в руководстве по Babel. -
eslintсflow-eslint. Инструкции по настройке см. в руководстве по ESLint.
См. также
- Массивы — коллекции переменной длины, элементы которых имеют один и тот же тип
- Объединения — объединения непересекающихся типов кортежей для безопасных с точки зрения типов размеченных шаблонов
- Объекты — ещё один базовый тип структуры данных во Flow
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/tuples