История вопроса: TypeScript расширяет обычные проверки типов, позволив разработчикам создавать свои пользовательские функции – type guards – которые проверяют, соответствует ли объект определенному типу. Это необходимо для работы с union-типами, динамическими структурами и API, где тип значения может быть разным.
Проблема часто в том, что обычные проверки типа через typeof и instanceof ограничены примитивами и классами, а вот структуры или сложные типы без этого не определишь. Нужно уметь явно подсказывать компилятору в каких случаях значение безопасно сужается до нужного типа.
Решение — писать функции-защитники вида function isCat(obj: Animal): obj is Cat {...}, где ключевой момент — type predicate в возвращаемом типе функции.
Пример кода:
interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }
Ключевые особенности:
Достаточно ли в type guard-функции вернуть true/false, чтобы компилятор сузил тип?
Нет. Важно явно указать возвращаемый тип в виде type predicate (например, pet is Dog), иначе TypeScript не будет сужать тип значения автоматически, даже если функция возвращает только true или false.
Можно ли использовать type guard внутри callback (например, в filter), и будет ли сужение работать корректно?
Да, если type guard правильно аннотирован, то компилятор сузит тип элементов массива после filter и внутри forEach/callback функций. Но если аннотация отсутствует или написана неверно, результат будет иметь тип-объединение (union) вместо конкретизированного типа.
const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript знает, что dogs: Dog[]
Чем пользовательские type guards отличаются от обычных проверок типа через typeof, instanceof?
Type guard-функции позволяют реализовать проверку любой структуры, описывать проверки любого уровня сложности, оперировать интерфейсами без класса, а не только базовыми типами и классами.
Функция фильтрует пользователей, делая type guard без type predicate:
function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]
Плюсы:
Минусы:
Функция фильтрует с корректным type predicate:
interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]
Плюсы:
Минусы: