生成不带对象副本的交叉点类型



我有一个服务调用,它给了我一个对象,我想在该对象中存储更多数据。我已经尽可能地抽象了它:

游乐场连接

//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 (I1&I2)
type target = (I1&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将是我最喜欢的,我如何才能编译它?

您可以使用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 & U {
Object.assign(target, source);
}

assign的返回类型为asserts target is T & U。因此,在调用assign(o1, {age: 144}之后,编译器将把o1视为I1 & {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 & U { }

在这种情况下,你在badAssign()中对编译器撒谎,任何使用它的人都会遇到麻烦:

badAssign(o1, { oops: "uh oh" });
o1.oops.toUpperCase(); // no compiler error, but runtime error!

所以要注意这种方法。


到代码的游乐场链接

我想你可以这样做:

const o2setprop:target = setProp(o1, "age", 144);

function setProp<T, P, K extends keyof any>(obj: T, k: K, val: P): T & { [_ in K]: P } {
const o = obj as any
o[k] = val
return o
}

最新更新