我正在处理查找类型,并希望构建一种安全的merge-util函数(该函数使用类型为T
的实体和包含T
键子集的对象进行更新)。我的目标是让编译器在我拼错属性或试图为T
附加不存在的属性时告诉我。
所以我有Person
,并在(v2.1)中使用内置的Partial
,如下所示:
interface Person {
name: string
age: number
active: boolean
}
function mergeAsNew<T>(a: T, b: Partial<T>): T {
return Object.assign({}, a, b);
}
现在我将此应用于以下数据:
let p: Person = {
name: 'john',
age: 33,
active: false
};
let newPropsOk = {
name: 'john doe',
active: true
};
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
mergeAsNew(p, newPropsOk);
mergeAsNew(p, newPropsErr); // <----- I want tsc to yell at me here because of trying to assign non-existing props
我希望TS编译器在第二次调用时对我大喊大叫,因为fullname
和enabled
不是Person
的道具。不幸的是,这在本地编译得很好,但是。。。当我在网上TS游乐场做同样的事情时,我或多或少得到了我所期望的:
The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'Person' is not a valid type argument because it is not a supertype of candidate '{ fullname: string; enabled: boolean; }'.
Property 'name' is missing in type '{ fullname: string; enabled: boolean; }'.
看起来游乐场使用的版本和我在当地使用的版本一样(2.1.4)。有人知道为什么这两个版本可能不同吗?
奖金问题:
当我尝试以下任务时:
let x: Person = mergeAsNew(p, newPropsOk);
我在x
上收到以下错误,但仅在操场上(本地一切正常):
Type '{ name: string; active: boolean; }' is not assignable to type 'Person'.
Property 'age' is missing in type '{ name: string; active: boolean; }'.
为什么?它不应该是Person
类型的吗?因为第一个mergeAsNew
参数是Person
,而其他所有参数都是Person
-props子集(所以最多是Person
)?
编辑这是我的tsconfig.json
:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"noEmitOnError": true,
"allowJs": false,
"sourceMap": true,
"strictNullChecks": true
},
"exclude": ["dist", "scripts"]
}
当你想表达一个类型只有另一个类型的属性子集时,普通的旧extends
也可以帮助
interface Person {
name: string
age: number
active: boolean
}
let p: Person = {
name: 'john',
age: 33,
active: false
};
let newPropsOk = {
name: 'john doe',
active: true
};
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
// a extends b and not the other way round
//because we don't want to allow b to have properties not in a
function mergeAsNew<T2, T1 extends T2>(a: T1, b: T2): T1 {
return Object.assign({}, a, b);
}
mergeAsNew(p, newPropsOk); // ok
mergeAsNew(p, newPropsErr);
// Argument of type 'Person' is not assignable
// to parameter of type '{ fullname: string; enabled: boolean; }'.
// Property 'fullname' is missing in type 'Person'.
PS不知道操场和地图类型发生了什么
好吧,看起来我昨天太执着了。我睡了一会儿,看起来(可能)不可能用这种方式做我想做的事(使用Partial
)。抛开这种奇怪的操场行为不谈,原因如下(至少我是这么认为的):
概括一下:
interface Person {
name: string
age: number
active: boolean
}
function mergeAsNew<T>(a: T, b: Partial<T>): T {
return Object.assign({}, a, b);
}
在该代码中,Partial<Person>
可以(但不必)包含任何Person
道具,因此以下是完全有效的Partial<Person>
类型:
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
由于Typescript有结构类型,它不关心额外的道具,它只检查对象的形状,这里的形状非常适合Partial<Person>
。
在Partial<Person>
中以某种方式检查参数形状以获得额外道具的唯一方法是将其作为对象文字传递(严格检查文字以匹配类型)
现在到mergeAsNew
函数本身。我在这里做Object.assign
,它完全是为它设计的——将两个参数中的道具合并到新对象中(所有这些)。由于类型仅用于TS编译,因此在运行时无法约束和选择性地从b
中选择道具,并应用它们来覆盖a
道具。
看起来@Artem的答案满足了我对extend
的需求,并且没有映射/查找类型。
无论如何,我仍然不明白为什么TS Playground会以这种方式工作(最初给我的印象是所有这些东西都是完全可行的)。