Partial
Герои постоянно меняются: растёт уровень, в бою убывает здоровье, находится золото. Обычно меняются лишь одно-два свойства, а остальные остаются прежними. В этом уроке вы познакомитесь со вспомогательными типами (utility types) — обобщёнными типами (generics), встроенными в TypeScript, которые делают новый тип из уже готового. Один из них вы уже знаете: Record<K, V>. Первый новый создан как раз для таких изменений. Но сначала — ещё одна возможность JavaScript, которая для него понадобится.
Spread. Три точки перед объектом внутри фигурных скобок, { ...kira }, копируют все его свойства в новый объект. Это называется spread (оператор расширения). Свойства, записанные после него, добавляются в новый объект или заменяют скопированные свойства с тем же именем:
const kira = { name: "Кира", level: 3, gold: 120 };
const stronger = { ...kira, level: 4 };
console.log(stronger);
console.log(kira);Программа выводит:
{ name: "Кира", level: 4, gold: 120 }
{ name: "Кира", level: 3, gold: 120 }Исходный объект остаётся как был: spread создаёт новый. Можно разворачивать несколько объектов сразу, { ...a, ...b }. Если в обоих есть свойство с одним и тем же именем, побеждает то, что идёт позже, здесь — из b. Три точки работают и в массивах: [...loot, "Старая карта"] — это новый массив со всем содержимым loot и ещё одним элементом в конце.
Partial. Теперь напишем функцию, которая обновляет героя: она получает героя и изменения, а возвращает нового, обновлённого героя. Но какой тип у изменений? С типом Hero TypeScript потребует все его свойства, хотя вы хотите изменить только два:
interface Hero {
name: string;
level: number;
health: number;
gold: number;
}
function updateHero(hero: Hero, changes: Hero): Hero {
return { ...hero, ...changes };
}
const kira: Hero = { name: "Кира", 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, healthМожно написать второй тип, где все свойства необязательные, но тогда при каждом изменении Hero придётся менять оба типа. Partial<Hero> делает это за вас: это Hero, у которого каждое свойство необязательное, как если бы вы написали name?: string, level?: number и так далее. С changes: Partial<Hero> функция принимает { level: 4, gold: 160 }, одно свойство или даже {}. А если вы добавите свойство в Hero, оно появится и в Partial<Hero>.
Необязательное не значит «что угодно». TypeScript по-прежнему проверяет имена и типы свойств:
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'.Внутри функции любого свойства changes может не быть, поэтому его тип включает undefined — как у необязательных свойств в уроке про типы объектов. { ...hero, ...changes } справляется с этим: то, чего нет в changes, ничего не заменяет, и у героя остаётся старое значение.
Типичные ошибки:
- Неверный порядок,
{ ...changes, ...hero }. Старые значения героя идут последними, поэтому заменяют изменения, и ничего не меняется. Изменения всегда пишутся последними. - Свойство перед spread, в котором оно тоже есть. В
{ level: 4, ...kira }собственныйlevelКиры заменил бы четвёрку, поэтому TypeScript сообщает:main.ts(2,20): error TS2783: 'level' is specified more than once, so this usage will be overwritten.
Задание
- 1
Под
kiraнапишите функциюupdateHero(hero: Hero, changes: Partial<Hero>): Hero, которая возвращает нового героя,{ ...hero, ...changes }. - 2
Кира выполнила квест: она достигла 4 уровня, и у неё 160 золота. Под функцией запишите
updateHero(kira, { level: 4, gold: 160 })в константуafterQuestи выведите её:{ name: "Кира", level: 4, health: 50, gold: 160 } - 3
Под ним выведите и
kira. Исходный герой не изменился:{ name: "Кира", level: 4, health: 50, gold: 160 } { name: "Кира", level: 3, health: 50, gold: 120 }