Зачем нужны обобщения
Некоторым функциям всё равно, что лежит в массиве. Функция, которая возвращает первый элемент, одинаково работает для имён, цен и героев. В JavaScript вы написали бы её один раз:
function first(items) {
return items[0];
}В TypeScript у параметра должен быть тип, иначе вы получите Parameter 'items' implicitly has an 'any' type. (TS7006). Но какой тип выбрать? В массиве может лежать что угодно. Попробуем варианты, которые вы уже знаете.
Вариант 1: any. Параметр типа any[] принимает любой массив, а функция возвращает any:
type Hero = { name: string; health: number };
// Возвращает первый элемент массива
function first(items: any[]): any {
return items[0];
}
const heroes: Hero[] = [
{ name: "Кира", health: 50 },
{ name: "Бруно", health: 70 },
];
const potions: string[] = ["Лечебное зелье", "Зелье маны"];
const leader = first(heroes);
console.log(`Лидер: ${leader.name}, здоровье ${leader.helth}`);
const potion = first(potions);
console.log(potion.toUppercase());Программа выводит:
Лидер: Кира, здоровье undefined
TypeError: potion.toUppercase is not a functionTypeScript не заметил ни одной из двух опечаток. Он не знает, что leader — герой, а potion — строка: у обеих тип any, а для any ничего не проверяется. Баги JavaScript вернулись: undefined вместо числа и сбой во время работы программы.
Вариант 2: unknown. В уроке «Объединения и сужение типов» вы познакомились с безопасной версией any. С unknown TypeScript не даст использовать результат, пока вы не сузите его тип:
function first(items: unknown[]): unknown {
return items[0];
}
const names = ["Кира", "Бруно", "Лена"];
const leader = first(names);
console.log(leader.toUpperCase());TypeScript сообщает:
main.ts(7,13): error TS18046: 'leader' is of type 'unknown'.Это безопасно, но мало полезно. Вы знаете, что первый элемент names — строка, а TypeScript забыл об этом по дороге через функцию. Пришлось бы после каждого вызова проверять typeof leader === "string".
Вариант 3: отдельная функция для каждого типа.
function firstName(names: string[]): string {
return names[0];
}
function firstHero(heroes: Hero[]): Hero {
return heroes[0];
}Теперь TypeScript проверяет всё: firstHero(heroes).helth — ошибка. Но у обеих функций одно и то же тело, и для каждого нового типа нужна ещё одна копия: firstPotion, firstQuest, firstMonster…
Нам нужна одна функция, которая говорит: «Какого бы типа ни были элементы, я верну элемент того же типа». В TypeScript именно для этого есть обобщения (generics) — тема этого урока. Свою первую обобщённую функцию вы напишете в следующем упражнении. А пока попробуйте вариант 3 в редакторе и посмотрите, к чему он приведёт.
Задание
- 1
Запустите программу: она выводит
здоровье undefinedилечит undefined, а TypeScript не жалуется. Напишите функциюfirstHero(heroes: Hero[]): Heroс тем же телом, что уfirst, и используйте её дляleaderвместоfirst. Тогда TypeScript найдёт опечатку в строке сleader.helth. Исправьте её. - 2
То же сделайте для зелий: напишите
firstPotion(potions: Potion[]): Potion, используйте её дляpotionи исправьте ошибку, которую найдёт TypeScript. Тогда программа выведет:Лидер: Кира, здоровье 50 Зелье: Лечебное зелье, лечит 20Теперь TypeScript проверяет обе строки. Но у вас две функции с одинаковым телом, а для следующего типа понадобилась бы третья. В следующем упражнении их заменит одна функция.