null и undefined
Иногда значения нет: у героя нет питомца, сундук пуст, поиск ничего не нашёл. В JavaScript для «ничего» есть два значения. undefined вы получаете из отсутствующего свойства или аргумента. null программисты записывают намеренно, чтобы сказать «здесь ничего нет». С null вы встречались в курсе JavaScript: querySelector() возвращал его, когда не находил элемент, и следующая строка падала с ошибкой TypeError: Cannot read properties of null. Такой сбой — одна из самых частых ошибок в программах на JavaScript.
В TypeScript у null и undefined есть собственные типы, а другие типы их не включают. Переменная типа string не может хранить null:
let target: string = null;main.ts(1,5): error TS2322: Type 'null' is not assignable to type 'string'.Когда значения может не быть, это записывают объединением: Pet | null — «питомец или ничего». И тогда TypeScript следит, чтобы вы проверили значение, прежде чем его использовать:
type Pet = { name: string; species: string };
type Hero = { name: string; pet: Pet | null };
function describePet(hero: Hero): string {
return `${hero.name}: питомец — ${hero.pet.species} ${hero.pet.name}`;
}main.ts(5,39): error TS18047: 'hero.pet' is possibly 'null'.
main.ts(5,59): error TS18047: 'hero.pet' is possibly 'null'.«Possibly null» («возможно, null») означает: когда функция выполняется, hero.pet может оказаться null, и тогда чтение hero.pet.species закончится ошибкой TypeError: Cannot read properties of null (reading 'species'). В JavaScript вы узнали бы об этом, когда программа упадёт, и только если кто-то вызовет функцию для героя без питомца. TypeScript сообщает об этом до запуска программы.
Исправление — проверка, и она сужает тип так же, как это делал typeof:
type Pet = { name: string; species: string };
type Hero = { name: string; pet: Pet | null };
function describePet(hero: Hero): string {
if (hero.pet === null) {
return `${hero.name}: питомца нет`;
}
return `${hero.name}: питомец — ${hero.pet.species} ${hero.pet.name}`;
}
const kira: Hero = { name: "Кира", pet: { name: "Пип", species: "сова" } };
const bruno: Hero = { name: "Бруно", pet: null };
console.log(describePet(kira));
console.log(describePet(bruno));Программа выведет:
Кира: питомец — сова Пип
Бруно: питомца нетНиже if с return hero.pet — это Pet. Можно проверить и наоборот: if (hero.pet !== null) { … }; тогда внутри фигурных скобок hero.pet — это Pet. Как видите, сужение работает и для свойств, а не только для переменных и параметров.
pet: Pet | null — это не то же самое, что необязательное свойство pet?: Pet. Здесь свойство должно быть всегда, а у героя без питомца pet: null. Необязательное свойство можно пропустить, и его тип — Pet | undefined: тогда в ошибке будет is possibly 'undefined' (TS18048), а проверять нужно через !== undefined.
Типичная ошибка: проверять не то «ничего». if (hero.pet !== undefined) не исключает null, поэтому для Pet | null TypeScript всё равно сообщит 'hero.pet' is possibly 'null'. Проверяйте то значение, которое есть в типе.
Задание
- 1
Запустите программу и исправьте ошибки с помощью проверки: если
targetравенnull, функция выводитАтаковать некогои больше ничего не делает. Тогда программа выведет:Волк: здоровья осталось 18 Атаковать некого - 2
Кира ещё не выбрала новую цель. Ниже вызовов создайте переменную (
let)currentTargetтипаMonster | nullсо значениемnullи вызовитеattack(currentTarget, 5). - 3
Затем Кира замечает гоблина. Положите в
currentTargetзначение{ name: "Гоблин", health: 20 }и ещё раз вызовитеattack(currentTarget, 5). Тогда программа выведет:Волк: здоровья осталось 18 Атаковать некого Атаковать некого Гоблин: здоровья осталось 15