Constraints
Let's write a generic function that finds the strongest of anything that has a power: heroes, monsters, spells.
function strongest<T>(things: T[]): T {
let best = things[0];
for (const thing of things) {
if (thing.power > best.power) {
best = thing;
}
}
return best;
}TypeScript reports:
main.ts(4,19): error TS2339: Property 'power' does not exist on type 'T'.
main.ts(4,32): error TS2339: Property 'power' does not exist on type 'T'.TypeScript is right: T can be any type, so strongest could be called with an array of numbers or texts, which have no power. You need to say which types are allowed. That's a constraint:
type Hero = { name: string; power: number };
type Monster = { kind: string; power: number; loot: string };
function strongest<T extends { power: number }>(things: T[]): T {
let best = things[0];
for (const thing of things) {
if (thing.power > best.power) {
best = thing;
}
}
return best;
}
const heroes: Hero[] = [
{ name: "Kira", power: 7 },
{ name: "Bruno", power: 9 },
];
const monsters: Monster[] = [
{ kind: "goblin", power: 3, loot: "Rusty Dagger" },
{ kind: "troll", power: 8, loot: "Big Club" },
];
console.log(strongest(heroes).name);
console.log(strongest(monsters).loot);This prints:
Bruno
Big ClubT extends { power: number } means: T can be any type that has a power which is a number (and anything else besides). It's the same word as in interface Boss extends Monster from lesson 4: “fits this shape, and may have more”. Inside the function, thing.power is now allowed. And the result keeps the full type: for heroes, strongest returns a Hero, so .name works; for monsters, a Monster with its loot.
Calls with arrays that don't fit the constraint are errors. Add these two lines at the end of the program:
const names = ["Kira", "Bruno"];
console.log(strongest(names));TypeScript reports:
main.ts(26,23): error TS2345: Argument of type 'string[]' is not assignable to parameter of type '{ power: number; }[]'.
Type 'string' is not assignable to type '{ power: number; }'.Why not without generics? A function strongest(things: { power: number }[]): { power: number } accepts the same arrays, but it returns just a { power: number }. TypeScript forgets that it was a hero, and strongest(heroes).name gives Property 'name' does not exist on type '{ power: number; }'. (TS2339). With a constraint, you get both: the constraint says what the function needs, and the type parameter keeps what the caller has.
The constraint can be any type, a named one too: with type Strong = { power: number };, write <T extends Strong>.
Instructions
Write a generic function findByName<T extends { name: string }>(things: T[], name: string): T | undefined. It returns the first element whose name property equals the parameter name, or undefined if there is none; use find.
Use findByName to print Bruno's heroClass, the price of the Silver Sword, and what it finds for the name "Dragon Egg" in the shop. (The results may be undefined, so use ?..) The program prints:
warrior
120
undefinedStart programming for free
7/9