Spec-Zone.ru › Flow

Кортежи

Типы кортежей представляют собой список фиксированной длины, элементы которого могут иметь разные типы.

const pair: [string, number] = ["age", 42];
Сравнение с TypeScript

Три формы кортежей в 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API