keyof
Sometimes the key you want to read is in a variable. In JavaScript, you'd write stats[key], with square brackets. In 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 reports:
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 refuses because key can be any text, "magik" for example, and Stats has only three keys. For any other key, stats[key] would be undefined, not a number, so TypeScript can't say what type it has (“implicitly has an 'any' type”).
The type for “one of the keys of Stats” is keyof Stats:
type Stats = { strength: number; agility: number; magic: number };
type StatName = keyof Stats; // "strength" | "agility" | "magic"keyof takes an object type and gives you the union of its keys, as literal types. Because it's computed from the type, it can't get out of date: add a key to Stats, and keyof Stats has it too. With key: keyof Stats, stats[key] is allowed, and a misspelled key is caught where you call the function:
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 reports:
main.ts(7,27): error TS2345: Argument of type '"magik"' is not assignable to parameter of type 'keyof Stats'.keyof with generics. When the properties have different types, keyof alone isn't enough. A monster has a name (a text) and health (a number). A function with key: keyof Monster returns string | number: TypeScript doesn't know which key you'll pass, so the result could be either. A generic function keeps the exact type:
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: "Troll", health: 80 };
console.log(getProperty(troll, "name").toUpperCase());
console.log(getProperty(troll, "health") - 30);This prints:
TROLL
50K extends keyof Monster is a constraint: K must be one of the keys. And Monster[K] is “the type of the property K of Monster”. In getProperty(troll, "name"), K is "name", so the result is a Monster["name"], which is a string; for "health", it's a number. (Lesson 9 has more about such types.)
Instructions
Change the type of the parameter key (with keyof) so that it accepts only the keys of Hero. TypeScript then finds the misspelled key in one of the calls. Fix it, so that the program prints:
name: Kira
level: 3Write a generic function getProperty<K extends keyof Hero>(hero: Hero, key: K): Hero[K] that returns the hero's property key.
Add these two lines at the end. They only work if getProperty returns the exact type: a text for "heroClass", a number for "gold".
console.log(getProperty(kira, "heroClass").toUpperCase());
console.log(getProperty(kira, "gold") + 50);The program prints:
name: Kira
level: 3
RANGER
170Start programming for free
8/9