Сужение типов и type guards

Как из «или одно, или другое» получить точный тип в нужной ветке кода.

Сужение типа (narrowing) — процесс, когда TypeScript по проверкам в коде определяет более конкретный тип значения в данной ветке.

Зачем сужать

Со значением union-типа нельзя сразу делать операции, специфичные для одного варианта — TypeScript не знает, что там сейчас. Нужно доказать компилятору конкретный тип проверкой. Тогда внутри ветки тип сужается, и доступны соответствующие методы.

typeof — для примитивов

Проверка typeof сужает примитивные типы:

function format(value: string | number): string {
  if (typeof value === "string") {
    return value.trim();      // здесь value: string
  }
  return value.toFixed(2);    // здесь value: number
}

После typeof value === "string" TypeScript уверен: в этой ветке value — строка, и метод trim() доступен. В else остаётся только number.

in — проверка свойства

Для объединения объектов typeof не годится (всё — "object"). Здесь помогает оператор in, проверяющий наличие свойства:

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

function speak(animal: Dog | Cat): void {
  if ("bark" in animal) {
    animal.bark();   // здесь animal: Dog
  } else {
    animal.meow();   // здесь animal: Cat
  }
}

instanceof — для классов

Если в union участвуют экземпляры классов, тип сужают через instanceof:

function getTime(value: Date | string): string {
  if (value instanceof Date) {
    return value.toISOString(); // value: Date
  }
  return value;                 // value: string
}

Проверяемый пример

function describe(value) {
  if (typeof value === "number") {
    return "Число: " + value.toFixed(1);
  }
  if (typeof value === "string") {
    return "Строка длиной " + value.length;
  }
  return "Что-то ещё";
}

console.log(describe(3.14159));
console.log(describe("привет"));
console.log(describe(true));

Вывод:

Число: 3.1
Строка длиной 6
Что-то ещё

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

Для сложных проверок пишут свою функцию-страж. Особый тип возврата value is Тип сообщает компилятору: если функция вернула true, значит тип именно такой:

interface Fish { swim(): void; }
interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird): void {
  if (isFish(pet)) {
    pet.swim(); // TypeScript знает: здесь pet — Fish
  } else {
    pet.fly();  // а здесь Bird
  }
}

Запись pet is Fish в типе возврата — предикат типа. Без него TypeScript считал бы, что функция вернула просто boolean, и не сужал бы тип. Это позволяет выносить сложную логику проверки в переиспользуемую функцию.

Памятка по сужению

ИнструментДля чего
typeofпримитивы: string, number, boolean
inналичие свойства у объекта
instanceofэкземпляры классов
value is Tпользовательский type guard

Итог

  • Сужение позволяет безопасно работать с union: внутри проверки тип становится конкретным.
  • typeof — для примитивов, in — по свойству, instanceof — для классов.
  • Type guard с возвратом value is T выносит сложную проверку в функцию и тоже сужает тип.
Проверьте себя
1. Что такое сужение типа (narrowing)?
AУдаление лишних свойств из объекта
BОпределение более конкретного типа значения внутри ветки по результату проверки
CПреобразование числа в строку
DУменьшение размера типа в памяти
2. Какой оператор подходит для сужения union из двух интерфейсов (Dog | Cat) по наличию метода?
Atypeof
Bin
Cinstanceof
Dникакой не подходит
3. Что означает тип возврата pet is Fish у функции?
AФункция всегда возвращает объект Fish
BЭто предикат типа: при возврате true компилятор сужает тип аргумента до Fish
CФункция принимает только Fish
DЭто синоним boolean без особого смысла
Поддержать проект