Сужение типа с typeof
Функции, которая получает string | number, обычно нужно обращаться с каждым типом по-своему. В JavaScript тип выясняют с помощью typeof, который вы знаете по курсу JavaScript: typeof 50 — это "number", а typeof "Серебряный меч" — "string". TypeScript понимает такую проверку. В этом примере гильдия выплачивает награды за квесты и оставляет себе 5 золотых из каждой золотой награды:
function payout(reward: string | number): string {
if (typeof reward === "string") {
return `Вы получаете предмет: ${reward.toUpperCase()}`;
} else {
return `Вы получаете золото: ${reward - 5} (5 забирает гильдия)`;
}
}
console.log(payout("Серебряный меч"));
console.log(payout(50));Программа выведет:
Вы получаете предмет: СЕРЕБРЯНЫЙ МЕЧ
Вы получаете золото: 45 (5 забирает гильдия)Внутри if TypeScript знает, что reward — это string, поэтому toUpperCase() разрешён. В ветке else остаётся только number, поэтому вычитание разрешено. Это называется сужением типа (narrowing): проверка делает тип внутри ветки уже — с string | number до просто string или просто number.
Без проверки TypeScript не разрешит вычитание:
function payout(reward: string | number): string {
return `Вы получаете золото: ${reward - 5} (5 забирает гильдия)`;
}main.ts(2,36): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.Это сообщение вам знакомо по уроку «Основные типы»: значение слева от - должно быть числом.
Сужение работает и после return. Если ветка if заканчивается на return, остальная часть функции выполняется только для другого типа, и TypeScript это знает:
function payout(reward: string | number): string {
if (typeof reward === "string") {
return `Вы получаете предмет: ${reward.toUpperCase()}`;
}
return `Вы получаете золото: ${reward - 5} (5 забирает гильдия)`;
}Ниже if reward — это number. Программисты часто пишут функции именно так: сначала особый случай, который завершается через return, а потом основной случай без лишних фигурных скобок.
Массивы тоже могут хранить объединения. TypeScript выводит для [12, "Старый ключ", 40] тип (string | number)[]: массив, элементы которого — строки или числа. Когда вы пишете такой тип сами, не забывайте про скобки. Без них number | string[] означает «число или массив строк», и TypeScript не принимает числа в массиве:
const loot: number | string[] = [12, "Старый ключ", 40];main.ts(1,34): error TS2322: Type 'number' is not assignable to type 'string'.
main.ts(1,53): error TS2322: Type 'number' is not assignable to type 'string'.Типичные ошибки:
typeofвыдаёт название типа строчными буквами:"string","number","boolean". Дляtypeof reward === "String"TypeScript сообщаетerror TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"String"' have no overlap.Длинный список в сообщении показывает все строки, которые может выдатьtypeof, а"String"среди них нет.- Сужение действует только внутри ветки или после
if, который завершается через return. Нижеif, который не завершается через return, тип сноваstring | number.
Задание
- 1
Запустите программу: TypeScript не разрешает
price * count, ведьpriceможет быть строкой. В началеformatPriceдобавьте проверкуtypeof: еслиprice— строка, функция возвращает её заглавными буквами. Тогда программа выведет:Цена: 50 БЕСПЛАТНО - 2
Ниже вызовов создайте массив
pricesтипа(number | string)[]со значениями12,"распродано"и40. - 3
Переберите
pricesцикломfor...ofи для каждой цены выведитеformatPrice(price, 2). Тогда программа выведет:Цена: 50 БЕСПЛАТНО Цена: 24 РАСПРОДАНО Цена: 80