Объединения литералов
Многие значения в игре берутся из короткого фиксированного списка. Герой — воин, маг или следопыт; на карте можно идти на север, юг, восток или запад. Такое значение можно хранить как string, но string — это любая строка, в том числе с опечатками:
function startingHealth(heroClass: string): number {
if (heroClass === "воин") {
return 120;
} else if (heroClass === "маг") {
return 70;
} else {
return 90;
}
}
console.log(startingHealth("воинн"));Программа выведет 90 — здоровье следопыта, потому что "воинн" — это не "воин". Ошибки нет, просто где-то в игре появляется неверное число, а причину можно искать долго.
В уроке «Основные типы» вы видели, что литеральный тип, например "воин", допускает ровно одно значение. С помощью | несколько литеральных типов можно соединить в объединение литералов и дать ему имя через type:
type HeroClass = "воин" | "маг" | "следопыт";HeroClass — это одна из этих трёх строк и ничего больше. Поставьте эту строку в начало программы выше, замените параметр на heroClass: HeroClass, и TypeScript проверит каждый вызов:
main.ts(13,28): error TS2345: Argument of type '"воинн"' is not assignable to parameter of type 'HeroClass'.Кавычки внутри кавычек означают в точности строку "воинн", как вы знаете по литеральным типам. Такая же защита работает везде, где используется тип. Для переменной TypeScript даже подсказывает, что вы, скорее всего, имели в виду:
type HeroClass = "воин" | "маг" | "следопыт";
let chosen: HeroClass = "воинн";main.ts(3,5): error TS2820: Type '"воинн"' is not assignable to type 'HeroClass'. Did you mean '"воин"'?Проверяет он и сравнения. Если бы опечатка была в функции, в if (heroClass === "воинн"), TypeScript сообщил бы:
main.ts(4,9): error TS2367: This comparison appears to be unintentional because the types 'HeroClass' and '"воинн"' have no overlap.«No overlap» («нет пересечения») означает, что ни одно значение не является одновременно HeroClass и строкой "воинн", так что такое сравнение никогда не будет истинным.
Одно может удивить. Если класс лежит в переменной let без типа, вызов — ошибка, хотя "маг" — это класс. С этими двумя строками под функцией:
let choice = "маг";
console.log(startingHealth(choice));main.ts(14,28): error TS2345: Argument of type 'string' is not assignable to parameter of type 'HeroClass'.TypeScript вывел для choice тип string, как он делает для каждого let со строкой: позже в неё можно было бы положить любую другую строку. Либо задайте переменной тип, let choice: HeroClass = "маг";, либо используйте const: тип const choice = "маг" — литеральный тип "маг", и он подходит.
Замечание на будущее: в других языках и в старом коде на TypeScript такие списки можно встретить в виде enum, например enum Direction { North, South }. В отличие от других типов, enum не исчезает, когда типы убирают: он превращается в отдельный код на JavaScript. Поэтому настройки этого сайта его не разрешают:
main.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.Новый код на TypeScript чаще использует вместо него объединения литералов, и вы тоже будете.
Задание
- 1
Программа выводит
90три раза, а у воина в начале игры должно быть 120 здоровья, а у мага — 70. Над функцией создайте типHeroClassдля трёх классов"воин","маг"и"следопыт"и сделайте его типом параметраheroClass. Тогда TypeScript найдёт две опечатки: исправьте их, чтобы программа вывела:120 70 90 - 2
Ниже вызовов создайте тип
Heroдля объекта со свойствомname(строка) и свойствомheroClassтипаHeroClass. Затем создайтеkiraтипаHero: её зовут"Кира", она"следопыт". - 3
Выведите строку о Кире, используя её свойства и
startingHealth. Тогда программа выведет:120 70 90 Кира — следопыт, стартовое здоровье: 90