keyof
Иногда ключ, который нужно прочитать, лежит в переменной. В JavaScript вы написали бы stats[key], с квадратными скобками. В TypeScript:
type Stats = { strength: number; agility: number; magic: number };
const kira: Stats = { strength: 6, agility: 9, magic: 4 };
function getStat(stats: Stats, key: string): number {
return stats[key];
}
console.log(getStat(kira, "agility"));TypeScript сообщает:
main.ts(4,12): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Stats'.
No index signature with a parameter of type 'string' was found on type 'Stats'.TypeScript отказывается, потому что key может быть любой строкой, например "magik", а в Stats всего три ключа. Для любого другого ключа stats[key] был бы undefined, а не числом, поэтому TypeScript не может сказать, какой у него тип («implicitly has an 'any' type»).
Тип «один из ключей Stats» записывается как keyof Stats:
type Stats = { strength: number; agility: number; magic: number };
type StatName = keyof Stats; // "strength" | "agility" | "magic"keyof берёт тип объекта и возвращает объединение его ключей в виде литеральных типов. Он вычисляется из типа, поэтому не может устареть: добавьте ключ в Stats, и он появится и в keyof Stats. С key: keyof Stats запись stats[key] разрешена, а ключ с опечаткой обнаруживается там, где вы вызываете функцию:
type Stats = { strength: number; agility: number; magic: number };
const kira: Stats = { strength: 6, agility: 9, magic: 4 };
function getStat(stats: Stats, key: keyof Stats): number {
return stats[key];
}
console.log(getStat(kira, "agility"));
console.log(getStat(kira, "magik"));TypeScript сообщает:
main.ts(7,27): error TS2345: Argument of type '"magik"' is not assignable to parameter of type 'keyof Stats'.keyof и обобщения. Когда у свойств разные типы, одного keyof мало. У монстра есть name (строка) и health (число). Функция с key: keyof Monster вернёт string | number: TypeScript не знает, какой ключ вы передадите, поэтому результат может быть любым из двух. Обобщённая функция сохраняет точный тип:
type Monster = { name: string; health: number };
function getProperty<K extends keyof Monster>(monster: Monster, key: K): Monster[K] {
return monster[key];
}
const troll: Monster = { name: "Тролль", health: 80 };
console.log(getProperty(troll, "name").toUpperCase());
console.log(getProperty(troll, "health") - 30);Программа выводит:
ТРОЛЛЬ
50K extends keyof Monster — это ограничение: K должен быть одним из ключей. А Monster[K] — это «тип свойства K у Monster». В getProperty(troll, "name") параметр K — это "name", поэтому результат имеет тип Monster["name"], то есть string; для "health" это number. (Подробнее о таких типах — в уроке «Вспомогательные типы».)
Задание
- 1
Измените тип параметра
key(с помощьюkeyof) так, чтобы он принимал только ключиHero. Тогда TypeScript найдёт ключ с опечаткой в одном из вызовов. Исправьте его, чтобы программа выводила:name: Кира level: 3 - 2
Напишите обобщённую функцию
getProperty<K extends keyof Hero>(hero: Hero, key: K): Hero[K], которая возвращает свойствоkeyгероя. - 3
Добавьте в конец две строки. Они работают, только если
getPropertyвозвращает точный тип: строку для"heroClass"и число для"gold".console.log(getProperty(kira, "heroClass").toUpperCase()); console.log(getProperty(kira, "gold") + 50);Программа выведет:
name: Кира level: 3 СЛЕДОПЫТ 170