#typescript
#typescript
Вопрос:
У меня есть вызов службы, который выдает мне объект, и я хочу сохранить больше данных в этом объекте. Я максимально абстрагировал его:
//in the real sample, I1 and the serviceCall are generated and I do not want to change them
interface I1{ name: string; }
function serviceCall():I1 {return {name:"Hello World"};}
const o1:I1 = serviceCall();
interface I2 { age: number; }
// I want to store the data of I2 in the same object, so my target type is (I1amp;I2)
type target = (I1amp;I2);
//solution 1: this copies all of o1 into a new object. I want no extra copy!
const o2copy:target = {...o1, age: 144};
//solution 2: casting
const o2cast:target = o1 as target;
//works but does not check anything!
//age is undefined until I set it but it does not give me any error if I leave the next line!
o2cast.age = 144;
//solution 3: simply setting the property: the javascript is fine, but typescript yields errors
o1.age = 144; //ERROR: property age does not exist on I1
const o2setprop:target = o1;
решение 3 было бы моим любимым, как я могу сделать эту компиляцию?
Комментарии:
1. Вы не можете выполнить эту компиляцию, вы либо выполняете приведение и гарантируете, что код безопасен вручную, если у вас действительно горячий цикл или что-то в этом роде, либо просто копируете, потому что это javascript, и последствия для производительности одной мелкой копии, вероятно, не имеют значения.
2. в действительности у меня есть массив объектов, и каждый объект также содержит больше данных. Я максимально уменьшил проблему здесь.
Ответ №1:
Вы можете использовать Object.assign()
для копирования свойств из {age: 144}
в ваш целевой o1
объект. Эта функция возвращает o1
, и возвращаемый тип сигнатуры типа стандартной библиотеки для нее является типом пересечения, по желанию:
const o2: target = Object.assign(o1, { age: 144 });
console.log(o2.name.toUpperCase()); // HELLO WORLD
console.log(o2.age.toFixed(1)); // 144.0
Обратите внимание, что пересечение — это всего лишь приближение к фактическому типу, который Object.assign()
возвращается. Если вы передаете параметры с противоречивыми свойствами, пересечение будет неточным. Однако в вашем примере кода это не проблема.
Однако компилятор все равно будет видеть o1
как тип I1
, поэтому вам придется начать использовать o2
исключительно и забыть о o1
:
o1.age; // compiler error!
// ~~~ <-- I1 has no property named "age"
Если вы действительно хотите сохранить o1
его без необходимости использовать для него новое имя переменной, вы можете рассмотреть возможность использования функций утверждения, представленных в TypeScript 3.7. Встроенной версии Object.assign()
, которая действует как функция утверждения, нет, но вы можете написать ее:
function assign<T, U>(target: T, source: U): asserts target is T amp; U {
Object.assign(target, source);
}
Возвращаемый тип assign
is asserts target is T amp; U
. Таким образом, после вашего вызова assign(o1, {age: 144}
компилятор будет рассматривать o1
как I1 amp; {age: number}
:
assign(o1, { age: 144 });
console.log(o1.name.toUpperCase()); // HELLO WORLD
console.log(o1.age.toFixed(1)); // 144.0
Возможно, вы хотите поступить именно так, но имейте в виду, что реализации функций утверждения не проверяются компилятором как безопасные для типов. Автор такой функции несет ответственность за то, чтобы после вызова функции действительно происходило сужение типа, указанное в ее возвращаемом типе. Ничто не предупредит вас, если, например, вы ничего не сделали внутри функции:
function badAssign<T, U>(target: T, source: U): asserts target is T amp; U { }
В таких случаях вы лжете компилятору внутри badAssign()
, и у любого, кто его использует, будут проблемы:
badAssign(o1, { oops: "uh oh" });
o1.oops.toUpperCase(); // no compiler error, but runtime error!
Так что будьте осторожны с этим подходом.
Комментарии:
1. Я знал о функциях утверждения, но мне никогда не приходило в голову, что они могут изменять «утвержденный» объект! При правильном именовании каждый член команды должен понимать, что он делает. спасибо за ваш подробный ответ!
Ответ №2:
Я думаю, вы можете это сделать:
const o2setprop:target = setProp(o1, "age", 144);
function setProp<T, P, K extends keyof any>(obj: T, k: K, val: P): T amp; { [_ in K]: P } {
const o = obj as any
o[k] = val
return o
}
Комментарии:
1. Мне больше нравится подход @jcalz, поскольку проще добавить более одного свойства за один раз. Я думаю, что мой вопрос в этом отношении был упрощен. Спасибо за ваш ответ. Пожалуйста, скажите мне, вам нужно было посмотреть описание этого типа, это действительно куча описаний 🙂
2. @fabsenet Это довольно простое определение типа, если вы знаете, как использовать сопоставленные типы. Сопоставленные, условные и рекурсивные типы, безусловно, являются лучшей функцией typescript, которую никто не знает, как использовать, имхо, каждый должен научиться!