Тип в 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
, чтобы сделать опциональными значения ключей, указанные в объектном типе.
Читайте также:
- Краткий обзор нововведений TypeScript 4.1
- Знакомство с SurrealDB с помощью Express.js, Node.js и TypeScript
- Как создать простую функцию AWS Lambda с помощью TypeScript
Читайте нас в Telegram, VK и Дзен
Перевод статьи Bytefer: How to Use TypeScript Intersection Types Like a Pro