Spec-Zone.ru › Flow

Пересечения

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

interface Serializable {
  serialize(): string;
}


interface HasLength {
  length: number;
}


function func(value: Serializable & HasLength) {
  // ...
}


func({
  length: 3,
  serialize() {
    return '3';
  },
}); // Works


func({length: 3}); // Error! Doesn't implement both interfaces
Сравнение с TypeScript

TypeScript регулярно объединяет типы объектов с помощью пересечения (A & {b: T}), тогда как в Flow для этого обычно используют разворачивание на уровне типов ({...A, b: T}). Типы объектов Flow по умолчанию точные, а пересечение точных объектов приводит к невозможному типу. Пересечения неточных объектов ({a: number, ...}) поддерживаются. Пересечения типов функций, используемые для выражения нескольких сигнатур вызова, работают так же, как в TypeScript. Полное сравнение см. в разделе Типизация разворачивания объектов.

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

Используйте пересечения, чтобы объявлять перегруженные функции с несколькими сигнатурами вызова или требовать, чтобы значение реализовывало несколько интерфейсов. Чтобы объединить точные типы объектов, используйте вместо этого разворачивание типов объектов — пересечение точных объектов создаёт невозможный тип.

Синтаксис типа-пересечения​

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

Type1 & Type2 & ... & TypeN

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

type Foo =
  & Type1
  & Type2
  & ...
  & TypeN

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

type Foo = Type1 & Type2;
type Bar = Type3 & Type4;

type Baz = Foo & Bar;

Типы-пересечения требуют соответствия всем типам на входе, но только одному на выходе​

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

type A = {a: number, ...};
type B = {b: boolean, ...};
type C = {c: string, ...};


function func(value: A & B & C) {
  const a: A = value;
  const b: B = value;
  const c: C = value;
}

Хотя мы рассматриваем значение только как один из типов, ошибки не возникает, поскольку оно удовлетворяет им всем.

Пересечение типов функций​

Типы-пересечения часто используют для описания функций, возвращающих разные результаты в зависимости от переданного входного значения. Предположим, например, что мы хотим описать тип функции, которая:

  • возвращает строку, если передать значение "string",
  • возвращает число, если передать значение "number", и
  • возвращает любой возможный тип (unknown), если передать любую другую строку.

Тип этой функции будет таким:

type Fn =
  & ((x: "string") => string)
  & ((x: "number") => number)
  & ((x: string) => unknown);

Каждая строка в приведённом выше определении называется перегрузкой, а функции типа Fn называются перегруженными.

Обратите внимание на скобки вокруг типов стрелочных функций. Они нужны, чтобы переопределить приоритет конструктора «стрелка» над пересечением.

Вызов перегруженной функции​

Используя приведённое выше определение, мы можем объявить функцию fn со следующим поведением:

declare const fn:
  & ((x: "string") => string)
  & ((x: "number") => number)
  & ((x: string) => unknown);


const s: string = fn("string"); // Works
const n: number = fn("number"); // Works
const b: boolean = fn("boolean"); // Error!

Flow обеспечивает такое поведение, сопоставляя тип аргумента с первой перегрузкой, у которой тип параметра совместим с ним. Например, обратите внимание, что аргумент "string" подходит и для первой, и для последней перегрузки. Flow выберет первую. Если ни одна перегрузка не подходит, Flow выдаст ошибку в месте вызова.

Объявление перегруженных функций​

Эквивалентный способ объявить ту же функцию fn — использовать несколько последовательных инструкций «declare function».

declare function fn(x: "string"): string;
declare function fn(x: "number"): number;
declare function fn(x: string): unknown;

Ограничение Flow состоит в том, что он не может проверить тело функции на соответствие типу-пересечению. Иными словами, если после приведённых выше объявлений мы добавим следующую реализацию для fn:

function fn(x: unknown) {
  if (x === "string") { return ""; }
  else if (x === "number") { return 0; }
  else { return null; }
}

Flow примет её без предупреждений (и выведет для неё тип Fn), но не проверит реализацию на соответствие этой сигнатуре. Поэтому такие объявления лучше подходят для определений библиотек, в которых реализации отсутствуют.

Пересечения типов объектов​

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

Например, пересечение двух неточных объектов с разными наборами свойств приведёт к объекту, содержащему все эти свойства.

type One = {foo: number, ...};
type Two = {bar: boolean, ...};


type Both = One & Two;


const value: Both = {
  foo: 1,
  bar: true
};

Если имена некоторых свойств совпадают, Flow применяет ту же стратегию, что и для перегруженных функций: возвращает тип первого свойства с таким именем.

Например, если объединить два неточных объекта со свойством prop, тип которого в первом объекте — number, а во втором — boolean, обращение к prop вернёт number.

type One = {prop: number, ...};
type Two = {prop: boolean, ...};


declare const both: One & Two;


const prop1: number = both.prop; // Works
const prop2: boolean = both.prop; // Error!

Для объединения точных типов объектов следует использовать вместо этого разворачивание типов объектов:

type One = {foo: number};
type Two = {bar: boolean};


type Both = {
  ...One,
  ...Two,
};


const value: Both = {
  foo: 1,
  bar: true
};

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

Невозможные типы-пересечения​

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

Например, можно создать пересечение числа и строки.

type NumberAndString = number & string;


function func(value: NumberAndString) { /* ... */ }


func(3.14); // Error!
func('hi'); // Error!

Но невозможно создать значение, которое одновременно является числом и строкой, хотя можно создать тип для такого значения. Практической пользы от создания подобных типов нет, но это побочный эффект работы типов-пересечений.

Случайно создать невозможный тип можно, пересёкши точные типы объектов. Например:

function func(obj: {a: number} & {b: string}) { /* ... */ }


func({a: 1}); // Error!
func({b: 'hi'}); // Error!
func({a: 1, b: 'hi'}); // Error!

Объект не может содержать ровно свойство a и никаких других свойств и одновременно содержать ровно свойство b и никаких других свойств.

См. также​

  • Объединения — двойственное понятие для пересечений: значения, являющиеся одним из типов заданного набора
  • Интерфейсы — распространённый способ определения типов, объединяемых пересечениями
  • Объекты — в том числе разворачивание типов объектов, предпочтительный способ объединения точных типов объектов

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

Spec-Zone.ru

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