Источник: Nuances of Programming
Тип в TypeScript можно определять как набор значений. Например, тип number можно представить как набор всех чисел. Из этого следует, что 1,0 и 68 принадлежат этому набору, а "bytefer" не принадлежит ему, поскольку относится к типу string.
Аналогичным образом объектные типы можно воспринимать как наборы объектов. Например, тип Point в следующем фрагменте кода представляет собой набор объектов со свойствами x и y, причем оба типа значений свойств являются числовыми. Тип Named представляет собой набор объектов, содержащих свойство name, а тип значения свойства — строковый.
interface Point {
x: number;
y: number;
}
interface Named {
name: string;
}
Согласно теории множеств, если A и B — множества, то множество, состоящее из всех элементов, принадлежащих множеству A и множеству B, является пересечением множества A и множества B.
При пересечении типов Point и Named создается новый тип. Объекты, содержащиеся в нем, принадлежат как Point, так и Named.
В TypeScript для реализации операции пересечения нескольких типов предусмотрен оператор &, а полученный новый тип называется типом пересечения.
Оператор & удовлетворяет следующим правилам.
- Идентичность: A & A эквивалентно A.
- Коммутативность (независимость от порядка выполнения): A & B эквивалентно B & A (за исключением сигнатур вызова и конструктора, как будет отмечено ниже).
- Ассоциативность: (A & B) & C эквивалентно A & (B & C).
- Сокращение супертипов: A & B эквивалентно A, если B является супертипом A.
В приведенном выше коде типы any и never являются специальными. Тип any, пересекающийся с типом any, приводит к типу any (что не происходит при пересечении any с never).
Теперь, когда мы познакомились с оператором &, посмотрим, какой тип получится при пересечения Point и Named.
Созданный тип NamedPoint содержит свойства x, y и name. Что же происходит, когда пересекаются несколько типов объектов, содержащих одинаковые атрибуты, но чьи свойства не являются однотипными?
interface X {
c: string;
d: string;
}
interface Y {
c: number;
e: string
}
type XY = X & Y;
type YX = Y & X;
В приведенном выше коде interface X и interface Y содержат одно и то же свойство c, но их типы не совпадают. Может ли в этом случае тип атрибута c в типе XY или YX быть строковым или числовым? Проверим это:
Почему после пересечения interface X и interface Y тип свойства c становится never? Потому что тип свойства c после этой операции — string & number, то есть он может быть либо строковым, либо числовым. Очевидно, что такого типа не существует, поэтому тип свойства c после операции — never.
В предыдущем примере получилось так, что типы свойства c в interface X и interface Y являются примитивными типами данных. Что же произойдет, если разные типы объектов содержат одно и то же свойство, а тип свойства не является примитивным? Рассмотрим конкретный пример:
Из приведенного выше результата ясно: при пересечении нескольких типов, если они содержат одни и те же свойства и при этом типы свойств является объектными, свойства объединяются по соответствующим правилам.
Помимо объектных типов, операции пересечения могут также выполняться с типами функций.
Как видно из приведенного выше кода, только оператор вызова функции f(1, "bytefer") выдает ошибку. Сообщение об ошибке выглядит следующим образом.
No overload matches this call.
Overload 1 of 2, '(a: string, b: string): void', gave the following error.
Argument of type 'number' is not assignable to parameter of type 'string'.
Overload 2 of 2, '(a: number, b: number): void', gave the following error.
Argument of type 'string' is not assignable to parameter of type 'number'.ts(2769)
Ни одна перегрузка не соответствует этому вызову.
Перегрузка 1 из 2, (a: string, b: string): void, выдала следующую ошибку.
Аргумент типа number не может быть присвоен параметру типа string.
Перегрузка 2 из 2, (a: число, b: число): void, привела к следующей ошибке.
Аргумент типа string не может быть присвоен параметру типа ‘number’.ts(2769).
Исходя из приведенного выше сообщения об ошибке, становится ясно: компилятор TypeScript будет использовать перегрузку функции для достижения операций пересечения различных типов функций. Чтобы решить эту проблему, определим новый тип функции F3 следующим образом.
Освоение типов пересечения в сочетании со знанием отображенных типов позволит реализовывать некоторые пользовательские типы утилит в соответствии с конкретными требованиями. Например, вы можете реализовать тип утилиты PartialByKeys, чтобы сделать опциональными значения ключей, указанные в объектном типе.
Читайте также:
Перевод статьи Bytefer: How to Use TypeScript Intersection Types Like a Pro