Kodokon kodokon.com

Сужение типов: typeof, in, instanceof и type guards

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

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Сужение типов (narrowing) - это механизм, с помощью которого TypeScript уточняет широкий тип (объединение, unknown) до более точного, анализируя поток управления. Ты уже пользуешься им, не задумываясь: каждый if над типизированным значением запускает такой анализ. Компилятор знает три семейства встроенных проверок: typeof для примитивов, instanceof для экземпляров классов и in для наличия свойства. Если выбирать их с умом, тебе не придётся прибегать к рискованным приведениям через as, которые выключают проверку вместо того, чтобы направлять её.

TYPESCRIPT
function formatValue(
  value: string | number | Date
): string {
  if (typeof value === "string") {
    return value.trim();
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value.toFixed(2);
}
Каждая ветка сужает тип; в последней может остаться только number.

Оператор in незаменим, когда ты работаешь со структурно разными объектами без поля-дискриминанта. Но будь внимателен: in проверяет наличие ключа, включая унаследованные, а не его тип. На внешних данных (API, JSON) он сужает тип по твоим объявлениям, а не по тому, что действительно есть во время выполнения. Классическая ловушка из практики: проверка typeof value === "object" пропускает null, потому что в JavaScript typeof null равен "object". Всегда добавляй явную проверку на null.

TYPESCRIPT
interface Cat { meow(): void }
interface Dog { bark(): void }

function speak(animal: Cat | Dog): void {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}
Сужение по наличию свойства с помощью оператора in.

Когда логика проверки становится переиспользуемой или слишком сложной для анализа потока, напиши собственный type guard: функцию, возвращаемый тип которой аннотирован как value is T. Компилятор тогда доверяет твоей реализации. Это контракт: если проверка неполная, ты возвращаешь в код ошибки времени выполнения, которых система типов уже не увидит. Начиная с TypeScript 5.5 простые предикаты выводятся автоматически в колбэках вроде filter, но для экспортируемых функций явная аннотация остаётся нормой.

TYPESCRIPT
interface User { id: string; email: string }

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "string" &&
    "email" in value &&
    typeof value.email === "string"
  );
}

const raw: unknown =
  JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
  console.log(raw.email.toLowerCase());
}
Предикат, который проверяет внешние данные до их использования.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Почему проверки typeof value === "object" недостаточно, чтобы сузить тип до объекта, отличного от null?
    • Потому что typeof возвращает "Object" с заглавной буквы
    • Потому что в JavaScript typeof null тоже равен "object"
    • Потому что typeof не работает с объединениями
    • Потому что массивы возвращают "array"
  2. Какая аннотация возвращаемого типа превращает функцию в собственный type guard?
    • boolean
    • value as User
    • value is User
    • asserts value
  3. Каков главный риск плохо написанного собственного type guard?
    • Ошибка компиляции внутри функции
    • Лживое сужение: компилятор верит в тип, который во время выполнения неверен
    • Потеря производительности компиляции