TypeScript Защитники типов

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

Они помогают TypeScript лучше понять и обеспечить безопасность типов, предоставляя явные проверки, которые определяют точный тип переменной во время выполнения программы.

Зачем использовать защитники типов?

  • Безопасность типов: гарантированная правильность операций только с соответствующими типами
  • Ясность кода: явный контроль типов и самодокументирование
  • Лучший инструментарий: получение точной поддержки автодополнения и завершения кода
  • Предотвращение ошибок: перехват ошибок, связанных с типами, ещё на этапе компиляции
  • Безопасность выполнения: дополнительный уровень проверки типов во время выполнения программы

Паттерны защитников типов

  • Защитники типов с typeof
  • Защитники типов с instanceof
  • Пользовательские защитники типов с предикатами типов
  • Дискриминантные объединения с литеральными типами
  • Защитники типов с оператором in
  • Функции утверждений типов

Защитники типов с typeof

Оператор typeof представляет собой встроенную защитную конструкцию, проверяющую тип примитивного значения во время выполнения.

Особенно полезен для сужения примитивных типов, таких как строки, числа, булевы значения и т.д.

Базовое применение

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

Пример


// Простая защитная конструкция с typeof
function formatValue(value: string | number): string {
  if (typeof value === 'string') {
    // TypeScript понимает, что значение имеет тип string
    return value.trim().toUpperCase();
  } else {
    // TypeScript понимает, что значение имеет тип number
    return value.toFixed(2);
  }
}

// Пример использования
const result1 = formatValue('  hello  ');  // "HELLO"
const result2 = formatValue(42.1234);      // "42.12"

В приведённом примере TypeScript ясно определяет тип переменной value в каждой ветви условного оператора.


Защитники типов с instanceof

Оператор instanceof проверяет, является ли объект экземпляром заданного класса или функции-конструктора.

Полезен для ограничения типов с использованием собственных классов или встроенных объектов.

Защита типов на основе классов

Сужайте объединения экземпляров классов, проверяя конструктор с помощью instanceof.

Пример


class Bird {
  fly() {
    console.log("Летит...");
  }
}

class Fish {
  swim() {
    console.log("Плывёт...");
  }
}

function move(animal: Bird | Fish) {
  if (animal instanceof Bird) {
    // TypeScript понимает, что животное относится к типу Bird
    animal.fly();
  } else {
    // TypeScript понимает, что животное относится к типу Fish
    animal.swim();
  }
}


Определённые пользователем защитные конструкции

Для более сложной проверки типов можно создать собственные функции защиты типов с использованием предикатов типов.

Это функции, возвращающие предикат вида parameterName is Type.

Функции-предикаты типов

Возвращайте предикат типа, например value is Type, чтобы TypeScript ограничивал тип в ветви истинности.

Пример


interface Car {
  make: string;
  model: string;
  year: number;
}

interface Motorcycle {
  make: string;
  model: string;
  year: number;
  type: "sport" | "cruiser";
}

// Функция-предикат
function isCar(vehicle: Car | Motorcycle): vehicle is Car {
  return (vehicle as Motorcycle).type === undefined;
}

function displayVehicleInfo(vehicle: Car | Motorcycle) {
  console.log(`Марка: ${vehicle.make}, Модель: ${vehicle.model}, Год выпуска: ${vehicle.year}`);

  if (isCar(vehicle)) {
    // TypeScript понимает, что транспортное средство относится к типу Car
    console.log("Это автомобиль");
  } else {
    // TypeScript понимает, что транспортное средство относится к типу Motorcycle
    console.log(`Это мотоцикл типа ${vehicle.type}`);
  }
}

Сигнатура функции vehicle is Car представляет собой предикат типа, который сообщает TypeScript, что тип сужается, когда функция возвращает true.


Дискриминантные объединения

Дискриминантные объединения (также известные как помеченные объединения) используют общее свойство (дискриминат) для идентифицировать различные объекты в объединении.

Данный паттерн особенно эффективен в сочетании с защитниками типов.

Базовое дискриминантное объединение

Используйте общее литеральное свойство (например, kind) для переключения и сужения до точного варианта.

Пример


interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function calculateArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      // TypeScript понимает, что фигура относится к типу Circle
      return Math.PI * shape.radius ** 2;
    case "square":
      // TypeScript понимает, что фигура относится к типу Square
      return shape.sideLength ** 2;
  }
}

Свойство kind используется как дискриминант для определения типа фигуры.


Оператор in

Оператор in проверяет наличие свойства у объекта.

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

Проверка существования свойства

Отсеивание членов объединения путем проверки существование конкретного свойства.

Пример


interface Dog {
  bark(): void;
}

interface Cat {
  meow(): void;
}

function makeSound(animal: Dog | Cat) {
  if ("bark" in animal) {
    // TypeScript понимает, что животное относится к типу Dog
    animal.bark();
  } else {
    // TypeScript понимает, что животное относится к типу Cat
    animal.meow();
  }
}


Функции утверждений типов

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

Полезны для проверки правильности данных во время выполнения программы.

Функции утверждений

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

Пример


// Функция утверждения типа
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Значение не является строкой');
  }
}

// Функция утверждения с пользовательским сообщением
function assert(condition: unknown, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

// Использование
function processInput(input: unknown) {
  assertIsString(input);
  // Теперь input имеет тип string
  console.log(input.toUpperCase());
}

// С пользовательской ошибкой
function processNumber(value: unknown): number {
  assert(typeof value === 'number', 'Значение должно быть числом');
  // Теперь value имеет тип number
  return value * 2;
}


Лучшие практики

Когда использовать каждую защитную конструкцию?

  • typeof — для примитивных типов (строки, числа, булевы значения и т.д.)
  • instanceof — для экземпляров классов и встроенных объектов
  • Пользовательские защитные конструкции — для сложной логики проверки
  • Дискриминантные объединения — для родственных типов с общим дискриминантом
  • Оператор in — для проверки существования свойства
  • Функции утверждений типов — для проверки данных во время выполнения с выдачей ошибок

Производительность

  • Операции typeof и instanceof выполняются очень быстро
  • Избегайте сложной логики в пользовательских защитных конструкциях там, где производительность критична
  • Рассмотрите использование предикатов типов для дорогостоящих проверок, используемых многократно