Spec-Zone.ru › Flow

Объединения

Тип-объединение представляет значение, которое является одним из набора типов.

function size(value: number | string): number {
  return typeof value === 'number' ? value : value.length;
}


size(1);       // Works!
size('three'); // Works!
size(true);    // Error!

Когда это использовать​

Используйте объединения, когда значение может иметь один из нескольких типов, например для функции, обрабатывающей разные виды событий или ответов. Для объединений строковых или числовых литералов (например, 'success' | 'error') рассмотрите возможность использовать вместо них перечисления Flow: они обеспечивают проверку полноты обработки и более структурированный API. Если вам нужен nullable-тип, используйте сокращённую запись maybe-типа ?T вместо записи T | null | void. Для значений, которые должны одновременно удовлетворять нескольким типам, а не одному из них, используйте пересечения.

Синтаксис типа-объединения​

Типы-объединения — это любое количество типов, соединённых вертикальной чертой |.

Type1 | Type2 | ... | TypeN

Также можно добавить вертикальную черту в начале — это удобно, если тип-объединение разбит на несколько строк.

type Foo =
  | Type1
  | Type2
  | ...
  | TypeN

Каждый элемент типа-объединения может иметь любой тип, в том числе быть другим типом-объединением.

type Numbers = 1 | 2;
type Colors = 'red' | 'blue'


type Fish = Numbers | Colors;

Если вы включили перечисления Flow, они могут стать альтернативой объединениям литеральных типов.

Сокращённые записи для объединений​

Объединение некоторого типа T с null или void встречается часто, поэтому для него предусмотрена сокращённая запись, называемая maybe-типами, с использованием префикса ?. Тип ?T эквивалентен типу T | null | void:

function maybeString(x: ?string) { /* ... */ }
maybeString('hi'); // Works!
maybeString(null); // Works!
maybeString(undefined); // Works!

Тип, который принимает любое возможное значение, — это unknown:

function everything(x: unknown) { /* ... */ }
everything(1); // Works!
everything(true); // Works!
everything(null); // Works!
everything({foo: 1}); // Works!
everything(new Error()); // Works!

Объединения и уточнения​

Если у вас есть значение типа-объединения, часто бывает полезно разделить его и отдельно обработать каждый из возможных типов. С типами-объединениями в Flow можно уточнить тип значения до одного конкретного типа.

Например, если у нас есть значение типа-объединения, которое может быть number, boolean или string, мы можем обработать случай с числом отдельно, используя оператор typeof из JavaScript.

function toStringPrimitives(value: number | boolean | string) {
  if (typeof value === 'number') {
    return value.toLocaleString([], {maximumSignificantDigits: 3}); // Works!
  }
  // ...
}

Проверив typeof нашего значения и выяснив, является ли оно number, Flow понимает, что внутри этого блока оно может быть только числом. Тогда мы можем написать код, который обрабатывает наше значение как число внутри этого блока.

Для типа-объединения достаточно одного типа на входе, но нужно учитывать все типы на выходе​

При вызове функции, принимающей тип-объединение, нужно передать одно из этих значений. Но внутри функции необходимо обработать все возможные типы.

Перепишем функцию так, чтобы она обрабатывала каждый тип отдельно, используя уточнения типов.

function toStringPrimitives(value: number | boolean | string): string {
  if (typeof value === 'number') {
    return String(value);
  } else if (typeof value === 'boolean') {
    return String(value);
  }
  return value; // If we got here, it's a `string`!
}

Если мы не обработаем каждый возможный тип нашего значения, Flow выдаст ошибку:

function toStringPrimitives(value: number | boolean | string): string {
  if (typeof value === 'number') {
    return String(value);
  }
  return value; // Error!
}

Непересекающиеся объединения объектов​

В Flow существует особый вид объединения, называемый «непересекающимся объединением объектов», который можно использовать с уточнениями типов. Такие объединения состоят из любого количества типов объектов, каждый из которых помечен одним свойством.

Например, представим, что у нас есть функция для обработки ответа сервера после отправки ему запроса. Если запрос выполнен успешно, мы получим объект со свойством type, которому присвоено значение 'success', и свойством value, содержащим обновлённые данные.

{type: 'success', value: 23}

Если запрос завершится ошибкой, мы получим объект, в котором type имеет значение 'error', а свойство error описывает ошибку.

{type: 'error', error: 'Bad request'}

Можно попытаться описать оба этих объекта одним типом объекта. Однако вскоре мы столкнёмся с проблемой: нам известно, что свойство существует, исходя из свойства type, а Flow этого не знает.

type Response = {
  type: 'success' | 'error',
  value?: number,
  error?: string
};


function handleResponse(response: Response) {
  if (response.type === 'success') {
    const value: number = response.value; // Error!
  } else {
    const error: string = response.error; // Error!
  }
}

Попытка объединить эти два отдельных типа в один лишь создаст проблемы.

Вместо этого, если создать тип-объединение из обоих типов объектов, Flow сможет определить, какой объект мы используем, исходя из свойства type.

type Response =
  | {type: 'success', value: number}
  | {type: 'error', error: string};


function handleResponse(response: Response) {
  if (response.type === 'success') {
    const value: number = response.value; // Works!
  } else {
    const error: string = response.error; // Works!
  }
}

Чтобы использовать этот шаблон, у каждого объекта в объединении должен быть общий ключ (в примере выше — type), а для этого ключа у каждого объекта должен быть указан свой литеральный тип (в нашем примере — строка 'success' и строка 'error'). Можно использовать любой вид литерального типа, в том числе числа и логические значения.

Непересекающиеся объединения с обобщёнными типами​

Для уточнения непересекающегося объединения дискриминирующее свойство должно иметь литеральный тип. Если дискриминирующее свойство задано обобщённым параметром типа, Flow не может сузить объединение, поскольку обобщённый тип ещё не разрешён до конкретного литерального типа:

type PayloadMap = {
  PING: string,
  PONG: number,
};


function handle<T extends keyof PayloadMap>(
  type: T,
  payload: PayloadMap[T],
) {
  if (type === 'PING') {
    payload as string; // Error! payload is still PayloadMap[T]
  }
}

Чтобы обойти это ограничение, перестройте код так, чтобы дискриминирующее свойство имело конкретный литеральный тип. Например, используйте непересекающееся объединение напрямую:

type Message =
  | {type: 'PING', payload: string}
  | {type: 'PONG', payload: number};


function handle(message: Message) {
  if (message.type === 'PING') {
    message.payload as string; // Works!
  }
}

Непересекающиеся объединения объектов с точными типами​

Для непересекающихся объединений нужно использовать одно свойство, чтобы различать типы объектов. Нельзя различать два разных неточных объекта по разным свойствам.

type Success = {success: true, value: boolean, ...};
type Failed  = {error: true, message: string, ...};


function handleResponse(response:  Success | Failed) {
  if (response.success) {
    const value: boolean = response.value; // Error!
  }
}

Это связано с тем, что в Flow можно передать значение объекта с большим количеством свойств, чем ожидает неточный тип объекта (благодаря структурной подтипизации).

type Success = {success: true, value: boolean, ...};
type Failed  = {error: true, message: string, ...};


function handleResponse(response:  Success | Failed) {
  // ...
}


handleResponse({
  success: true,
  error: true,
  value: true,
  message: 'hi'
});

Если объекты никак не конфликтуют друг с другом, различить их невозможно.

Однако эту проблему можно обойти, используя точные типы объектов.

type Success = {success: true, value: boolean};
type Failed  = {error: true, message: string};


type Response = Success | Failed;


function handleResponse(response: Response) {
  if (response.success) {
    const value: boolean = response.value;
  } else {
    const message: string = response.message;
  }
}

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

Непересекающиеся объединения кортежей​

Как и описанные выше непересекающиеся объединения объектов, можно также определить непересекающиеся объединения кортежей. Это объединения типов кортежей, в которых каждый кортеж помечен определённым элементом. Например:

type Response =
  | ['success', number]
  | ['error', string];


function handleResponse(response: Response) {
  if (response[0] === 'success') {
    const value: number = response[1]; // Works!
  } else {
    const error: string = response[1]; // Works!
  }
}

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

function prettyPrint(
  ...args: ['currency', dollars: number, cents: number]
         | ['choice', boolean]
): string {
  switch (args[0]) {
    case 'currency':
      return args[1] + '.' + args[2];
    case 'choice':
      return args[1] ? 'yes' : 'no';
  }
}
// Argument types based on the first arg
prettyPrint("currency", 1, 50); // OK
prettyPrint("choice", true); // OK


prettyPrint("currency", 1); // ERROR - missing arg
prettyPrint("currency", true); // ERROR - wrong type arg

См. также​

  • Пересечения — двойственное понятие к объединениям: значения, которые принадлежат всем типам из набора
  • Уточнения типов — как сузить типы-объединения до конкретных элементов
  • Maybe-типы — сокращённая запись для T | null | void
  • Литеральные типы — использование конкретных значений в качестве типов, часто в сочетании с объединениями
  • Перечисления Flow — структурированная альтернатива объединениям литеральных типов
  • Выражения match — сопоставление с образцом для полной обработки элементов объединения

© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/unions

Spec-Zone.ru

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