Урок 07

Union и литеральные типы

Union

Union (|) — тип, который означает «одно из нескольких»:

let id: string | number;
id = 'abc';
id = 42;

Функция с union:

function print(value: string | number) {
    console.log(value);
}

Сужение типа

Чтобы использовать специфичные методы — нужно сузить тип:

function print(value: string | number) {
    if (typeof value === 'string') {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

typeof — один из способов сужения.

Литеральные типы

Тип — конкретное значение:

type Status = 'idle' | 'loading' | 'success' | 'error';

let status: Status = 'idle';
status = 'loading'; // ок
status = 'done';    // ошибка

Числовые литералы:

type Dice = 1 | 2 | 3 | 4 | 5 | 6;
const roll: Dice = 4;

Булевы:

type Yes = true;

Комбинация с объектами

type Button = {
    variant: 'primary' | 'secondary' | 'danger';
    size: 'sm' | 'md' | 'lg';
};

Discriminated unions

Мощный паттерн: union объектов с общим полем-дискриминатором:

type Shape =
    | { kind: 'circle'; radius: number }
    | { kind: 'square'; side: number }
    | { kind: 'rectangle'; width: number; height: number };

Поле kind — дискриминатор. По нему TypeScript понимает, какой вариант используется:

function area(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'square':
            return shape.side ** 2;
        case 'rectangle':
            return shape.width * shape.height;
    }
}

В каждом case доступны только соответствующие поля.

Тип null и undefined

type User = {
    name: string;
    email: string | null;
};

if (user.email !== null) {
    console.log(user.email.toLowerCase());
}

Exhaustive check

TypeScript следит, что все варианты обработаны:

function area(shape: Shape): number {
    switch (shape.kind) {
        case 'circle': return Math.PI * shape.radius ** 2;
        case 'square': return shape.side ** 2;
        case 'rectangle': return shape.width * shape.height;
        default:
            const _exhaustive: never = shape;
            throw new Error('Unknown shape');
    }
}

Если добавишь новый вариант в Shape и забудешь обработать — TypeScript выдаст ошибку.

Union vs enum

// Union — предпочтительно
status: 'idle' | 'loading' | 'success';

// Enum — старый подход
enum Status {
    Idle = 'idle',
    Loading = 'loading',
}

Union проще и не создаёт runtime-код.

Итоги

  • Union | — одно из нескольких
  • typeof сужает тип
  • Литеральные типы — конкретные значения
  • Discriminated unions — мощный паттерн
  • never для exhaustive check
  • Union предпочтительнее enum