Урок 07

Type guards

Type guard — проверка, которая сужает тип. TypeScript понимает такие проверки и даёт доступ к полям.

typeof

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

Работает со string, number, boolean, symbol, undefined, object, function.

in

Проверяет наличие свойства:

type A = { a: string };
type B = { b: number };

function handle(value: A | B) {
    if ('a' in value) {
        console.log(value.a);
    } else {
        console.log(value.b);
    }
}

instanceof

Для классов:

class Dog { bark() {} }
class Cat { meow() {} }

function sound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}

Array.isArray

function print(value: string | string[]) {
    if (Array.isArray(value)) {
        value.forEach((v) => console.log(v));
    } else {
        console.log(value);
    }
}

Литеральный дискриминатор

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

function area(shape: Shape) {
    if (shape.kind === 'circle') {
        return Math.PI * shape.radius ** 2;
    }
    return shape.side ** 2;
}

Пользовательские type guards

Функция с возвращаемым типом value is Type:

function isString(value: unknown): value is string {
    return typeof value === 'string';
}

function print(value: unknown) {
    if (isString(value)) {
        console.log(value.toUpperCase());
    }
}

Guard для объектов

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

function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'name' in value &&
        typeof (value as User).name === 'string'
    );
}

if (isUser(data)) {
    console.log(data.name);
}

Guard для массива

function isUserArray(value: unknown): value is User[] {
    return Array.isArray(value) && value.every(isUser);
}

Проверка на null

if (value !== null) {
    //
}

if (value != null) { // null и undefined
    //
}

Assertion functions

function assert(condition: unknown, message: string): asserts condition {
    if (!condition) throw new Error(message);
}

function print(value: string | null) {
    assert(value !== null, 'Value is null');
    console.log(value.toUpperCase());
}

asserts condition — после вызова TypeScript уверен в условии.

Assertion для типа

function assertIsString(value: unknown): asserts value is string {
    if (typeof value !== 'string') {
        throw new Error('Not a string');
    }
}

Guard и discriminated unions

Дискриминатор — самый надёжный guard для union объектов:

type Notification =
    | { kind: 'email'; address: string }
    | { kind: 'sms'; phone: string };

function send(n: Notification) {
    if (n.kind === 'email') {
        // n: { kind: 'email'; address: string }
    }
}

Когда создавать guard

  • Данные из API (валидация)
  • Парсинг JSON
  • Обработка unknown
  • Сложные проверки формы
  • Многократное использование проверки

Итоги

  • Type guards сужают тип
  • Встроенные: typeof, in, instanceof, Array.isArray
  • Пользовательские — value is Type
  • Assertion — asserts condition
  • Дискриминатор — самый надёжный
  • Полезно для API и unknown