Пересечения
Тип-пересечение представляет значение, которое одновременно является всеми типами из заданного набора.
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 регулярно объединяет типы объектов с помощью пересечения (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