Сужение типов и 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выносит сложную проверку в функцию и тоже сужает тип.