Перейти к содержанию

Совместимость объединений

Поскольку понимание поведения типа Union при проверке на совместимость может вызывать противоречия, эта совсем крохотная глава, будет посвящена этому механизму.

Совместимость

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

Как известно к данному моменту, объектный тип A совместим с объектным типом B, если первый содержит все обязательные признаки второго. Кроме того члены участвующие в проверке на совместимость не обязаны принадлежать к идентичным типам, достаточно чтобы они также были совместимы. Утрированно всё сказанное можно перефразировать как - тип обладающий большим количеством совместимых признаков совместим с типом обладающим меньшим количеством признаков. Или даже - больший тип совместим с меньшим типом.

interface Smaller {
  a: number;
  b: string;
}
interface Bigger {
  a: number;
  b: string;
  c: boolean;
}

declare let small: Smaller;
declare let big: Bigger;

let s: Smaller = big; // Ok
let b: Bigger = small; // Error

Любому разработчику начавшему свою карьеру с языка реализующего ооп парадигму, подобное поведение кажется само собой разумеющимся. Так вот, с типом объединение (Union) все в точности наоборот. Точнее может показаться что наоборот, хотя на самом деле это совершенно другой случай.

type Smaller = number | string;
type Bigger = number | string | boolean;

declare let small: Smaller;
declare let big: Bigger;

let s: Smaller = big; // Error
let b: Bigger = small; // Ok

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

В нашем примере переменная с типом Bigger помимо прочего может быть ассоциирована со значением принадлежащим к типу boolean, который не определяет множество типа Smaller.

type Smaller = number | string;
type Bigger = number | string | boolean;

declare let small: Smaller;
declare let big: Bigger;

/**[0] */
let s: Smaller = big; // Error
/**[1] */
let b: Bigger = small; // Ok

/**
 * [0] переменная big может иметь значение принадлежащие
 * к типу boolean которое отсутствует в типе Smaller. Поэтому
 * переменная big не может быть присвоенна переменной с Smaller.
 *
 * [1] И наоборот. Поскольку переменная small может иметь значение
 * принадлежащие либо к number либо string, её можно присвоить пепеменной
 * с типом Bigger поскольку множество определяющее его включает данные типы.
 */