Как работают type guards в TypeScript
Что проверяют
Понимаете ли вы, что сужение типов работает только по коду, который анализатор умеет разобрать, и что предикат x is T это обещание компилятору, которое он не проверяет. Вопрос отделяет тех, кто типизирует внешние данные осознанно, от тех, кто расставляет as.
Как отвечать
- Встроенные сужения:
typeof x === "string",x instanceof Date,Array.isArray(x), операторin, проверка наnullиundefined, сравнение поля-дискриминанта с литералом. - Основной инструмент для объединений это дискриминированный union: общее литеральное поле (
kind,status) иswitch, где ветка по умолчанию принимаетnever, чтобы новый вариант ломал сборку. - Пользовательский type guard это функция с типом возврата
value is T. Компилятор верит ей на слово и тело не проверяет, поэтому она должна быть короткой. - Assertion-функция
function assertUser(v: unknown): asserts v is Userсужает тип после вызова и бросает исключение. Требует явной аннотации, у стрелки вconstне выводится. - С TypeScript 5.5 предикат выводится сам, если функция просто возвращает результат проверки:
arr.filter(x => x !== null)уже даёт сужённый тип. - Для данных из сети практичнее схема (zod, valibot): один источник и типа, и валидации.
type Ok = { status: "ok"; data: string };
type Err = { status: "err"; code: number };
function render(r: Ok | Err): string {
if (r.status === "ok") return r.data;
return `Ошибка ${r.code}`;
}Где спотыкаются
Пишут предикат с параметром any и проверкой одного поля. Тип сужается до полного интерфейса, данные ему не соответствуют, падение уезжает в рантайм. Параметр берут unknown и проверяют все обещанные поля.
Забывают, что typeof null === "object", и внутри ветки получают null там, где ждали объект.
Полагаются на instanceof для значений из другого realm (iframe, worker) или после сериализации. Прототип там другой, проверка молча ложна.
Теряют сужение после await, колбэка или вызова функции: анализ сбрасывает сужение изменяемого свойства объекта. Помогает копия в const.