Свои проверки типа
Сложную проверку на сортировочной ленте выносят в отдельную будку. Работник будки вскрывает посылку, смотрит, что внутри, и ставит штамп «курьер». Дальше посылку никто не вскрывает: её направляют по штампу. Лента верит штампу на слово.
В программе такая будка — функция-проверка. Проверку "phone" in d из урока про in удобно вынести в функцию: правило записано один раз, а вызвать его можно в любом месте программы. Но если объявить, что функция возвращает boolean, сужение пропадёт:
type Courier = { phone: string };
type Pickup = { address: string };
function isCourier(d: Courier | Pickup): boolean {
return "phone" in d;
}
function contact(d: Courier | Pickup) {
if (isCourier(d)) {
return "call " + d.phone; // ✗
}
return "pick up";
}error TS2339: Property 'phone' does not exist on type 'Courier | Pickup'.
Property 'phone' does not exist on type 'Pickup'.Для tsc результат isCourier(d) — обычное true или false. Из типа boolean не видно, что true значит «это курьер», поэтому в ветке у d остаётся всё объединение.
Предикат типа
Чтобы функция сужала тип, вместо boolean пишут предикат типа — запись d is Courier. Она читается так: «если функция вернула true, то d — это Courier»:
type Courier = { phone: string };
type Pickup = { address: string };
function isCourier(d: Courier | Pickup): d is Courier {
return "phone" in d;
}
function contact(d: Courier | Pickup) {
if (isCourier(d)) {
return "call " + d.phone;
}
return "pick up at " + d.address;
}
console.log(contact({ address: "Main St 5" })); // pick up at Main St 5Давайте разберём пример по строкам:
d is Courierстоит на месте типа результата. При запуске функция возвращает обычныйtrueилиfalse.- Слева от
is— имя параметра функции: предикат говорит именно оd. - Внутри
if (isCourier(d))уdтипCourier, как после проверки"phone" in d. - После
returnосталсяPickup: предикат сужает и ветку, где функция вернулаfalse. - Программа печатает
pick up at Main St 5.
Это и есть штамп из будки. Функция один раз смотрит на объект, а if в любом месте программы сужает тип по её ответу.
Здесь аналогия со штампом перестаёт работать. Штамп остаётся на посылке, а предикат ничего не оставляет на объекте. После компиляции от d is Courier остаётся обычная функция, которая возвращает true или false.
Когда TypeScript выводит предикат сам
С версии 5.5 TypeScript умеет выводить предикат сам. Уберите : boolean из первого примера — и подсказка редактора над isCourier покажет function isCourier(d: Courier | Pickup): d is Courier, а сужение заработает.
Так получается не всегда. Предикат выводится для функции без типа результата, в которой один return и возвращается сама проверка. Если в теле несколько return, например if ("phone" in d) { return true; } return false;, тип результата снова boolean. В таких случаях, а также когда обещание хочется видеть в объявлении функции, предикат пишут явно.
На собеседовании об этом часто спрашивают: как написать свою функцию, которая сужает тип? Ответ: объявить её результат предикатом x is T.
Частая ошибка
В теле функции-проверки перепутали ключ:
type Courier = { phone: string };
type Pickup = { address: string };
function isCourier(d: Courier | Pickup): d is Courier {
return "address" in d; // ✗
}
function contact(d: Courier | Pickup) {
if (isCourier(d)) {
return "call " + d.phone.trim();
}
return "pick up at " + d.address;
}
console.log(contact({ address: "Main St 5" }));tsc молчит: он не сверяет тело функции с её предикатом. Функция возвращает true для самовывоза, а tsc верит предикату и считает d курьером. Поэтому он пропускает d.phone.trim(), хотя у самовывоза телефона нет. При запуске программа падает:
TypeError: Cannot read properties of undefined (reading 'trim')Предикат — обещание, и проверять его приходится вам, а не компилятору. Неверное тело сужает тип неверно, и ошибка всплывает только при запуске.
Главное
- Функция с типом результата
booleanне сужает тип там, где её вызвали. - Предикат
d is Courierвместоbooleanсужает тип: приtrueдоCourier, приfalseдо остальных вариантов. - TypeScript 5.5 и новее сам выводит предикат, если у функции нет типа результата, а её единственный
returnвозвращает саму проверку. - tsc верит предикату на слово: неверное тело сузит тип неверно, и ошибка всплывёт при запуске.