История вопроса:
С развитием TypeScript появилась задача надёжно определять более узкий тип переменной в логических ветках. Классические проверки типов (через typeof или instanceof) не всегда достаточны, особенно если у объекта сложная структура или иерархия. Для повышения безопасности данных и удобства TypeScript реализовал механизм type predicates для создания пользовательских type guards.
Проблема:
Обычные проверки типа внутри функций не дают компилятору информации о типе переменной в последующем коде, если использовать только результат true/false. Компилятор не "понимает", что именно проверено. А это приводит к неявным ошибкам в рантайме, когда мы по ошибке обращаемся к несуществующим свойствам.
Решение:
Типы-утверждения (type predicates) с помощью типа-подстановки в виде 'param is Type' дают компилятору понять, что с этим параметром после вызова проверки можно работать, как с определённым типом. Такие функции увеличивают типобезопасность и расширяют систему сужения типов под любые сложные задачи.
Пример кода:
interface Bird { fly(): void; feathers: boolean; } interface Fish { swim(): void; fins: number; } function isBird(animal: Bird | Fish): animal is Bird { return (animal as Bird).fly !== undefined; } const pet: Bird | Fish = ...; if (isBird(pet)) { pet.fly(); // OK: pet теперь Bird } else { pet.swim(); // OK: pet теперь Fish }
Ключевые особенности:
Может ли type guard-функция работать, если явно не указать в сигнатуре возвращаемый тип 'param is Type'?
Нет, если не указать явно 'param is Type' в сигнатуре, TypeScript не сможет сузить тип в ветках кода, несмотря на возвращаемое значение true/false. Компилятор не поймёт, что параметр можно использовать как определённый тип.
Пример кода:
function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // Работает? if (isFish(pet)) { pet.swim(); // Ошибка: Property 'swim' does not exist }
Можно ли использовать type predicates для проверки примитивных значений, таких как строка или число?
Да, можно, но чаще используется typeof и такие guards становятся избыточными. Тем не менее, ничто не мешает реализовать пользовательский guard:
function isString(x: unknown): x is string { return typeof x === "string"; }
Обеспечивает ли type guard-функция строгую защиту от ошибок типа на этапе компиляции?
Не полностью. TypeScript полагается на реализацию самой функции и не может проверить корректность логики внутри неё. Если вы неверно реализуете проверку — компилятор не поймёт ошибку, возникнут проблемы на этапе выполнения.
function isFish(animal: Fish | Bird): animal is Fish { // Ошибочно: всегда возвращает true return true; }
** Негативный кейс Разработчик реализовал функцию-предикат, но допустил ошибку в проверке структуры, из-за чего функция возвращала true всегда. Код проходил компиляцию, но на этапе рантайма происходил вызов несущ. метода.
Плюсы:
Минусы:
** Позитивный кейс Type predicate-функции реализованы корректно, протестированы unit-тестами на граничных значениях и ошибочных данных.
Плюсы:
Минусы: