Partial
Heroes change all the time: they level up, lose health in a fight, find gold. Usually only one or two properties change, and the rest stay the same. In this lesson, you'll meet utility types: generic types built into TypeScript that make a new type out of a type you already have. You already know one of them, Record<K, V>. The first new one is made for changes like these. But first, a new piece of JavaScript that you'll need for it.
Spread. Three dots in front of an object inside braces, { ...kira }, copy all its properties into a new object. This is called spread. Properties written after it are added to the new object, or they replace the copied ones with the same name:
const kira = { name: "Kira", level: 3, gold: 120 };
const stronger = { ...kira, level: 4 };
console.log(stronger);
console.log(kira);This prints:
{ name: "Kira", level: 4, gold: 120 }
{ name: "Kira", level: 3, gold: 120 }The original object stays as it was: spread makes a new one. You can spread several objects, { ...a, ...b }. When both have a property with the same name, the one that comes later wins, here the one from b. The three dots work in arrays too: [...loot, "Old Map"] is a new array with everything from loot and one more item at the end.
Partial. Now a function that updates a hero: it gets the hero and the changes, and it returns a new, updated hero. But what type should the changes have? With Hero, TypeScript wants all its properties, even though you want to change only two:
interface Hero {
name: string;
level: number;
health: number;
gold: number;
}
function updateHero(hero: Hero, changes: Hero): Hero {
return { ...hero, ...changes };
}
const kira: Hero = { name: "Kira", level: 3, health: 50, gold: 120 };
const afterQuest = updateHero(kira, { level: 4, gold: 160 });main.ts(13,37): error TS2739: Type '{ level: number; gold: number; }' is missing the following properties from type 'Hero': name, healthYou could write a second type with every property optional, but then you'd have to change both types whenever Hero changes. Partial<Hero> does it for you: it's Hero with every property optional, as if you'd written name?: string, level?: number and so on. With changes: Partial<Hero>, the function accepts { level: 4, gold: 160 }, a single property, or even {}. And when you add a property to Hero, Partial<Hero> has it too.
Optional doesn't mean that anything goes. TypeScript still checks the names and the types of the properties:
updateHero(kira, { helth: 60 });
updateHero(kira, { health: "60" });main.ts(13,20): error TS2561: Object literal may only specify known properties, but 'helth' does not exist in type 'Partial<Hero>'. Did you mean to write 'health'?
main.ts(14,20): error TS2322: Type 'string' is not assignable to type 'number'.Inside the function, any property of changes may be missing, so its type includes undefined, just like the optional properties in the lesson on object types. { ...hero, ...changes } takes care of that: what's missing in changes doesn't replace anything, so the hero keeps its old value.
Typical mistakes:
- The wrong order,
{ ...changes, ...hero }. The hero's old values come last, so they replace the changes, and nothing changes. The changes always go last. - A property written before a spread that contains it too. In
{ level: 4, ...kira }, Kira's ownlevelwould replace the 4, so TypeScript reports:main.ts(2,20): error TS2783: 'level' is specified more than once, so this usage will be overwritten.
Instructions
Under kira, write a function updateHero(hero: Hero, changes: Partial<Hero>): Hero that returns a new hero, { ...hero, ...changes }.
Kira has finished a quest: she's reached level 4 and has 160 gold. Under the function, store updateHero(kira, { level: 4, gold: 160 }) in a constant afterQuest and print it:
{ name: "Kira", level: 4, health: 50, gold: 160 }Under it, print kira too. The original hero hasn't changed:
{ name: "Kira", level: 4, health: 50, gold: 160 }
{ name: "Kira", level: 3, health: 50, gold: 120 }Start programming for free
1/7