问题:
当我忘记将嵌套字段添加到类型 T
的对象时,为什么我没有收到编译时错误,当使用对象传播构造所述对象时?
示例:
interface User {
userId: number;
profile: {
username: string
}
}
function updateUsername(user: User): User {
return {
...user,
profile: {
// Error (as expected)
}
}
}
function updateUsernameGeneric<T extends User>(user: T): T {
return {
...user,
profile: {
// No error (unexpected... why?)
}
}
}
我自己对答案的猜测:我所能想象的是,TypeScript 允许子类型删除其父类(super class)的属性,这使得对于某些子类型
T
成为可能。的 User
, profile
属性可能不包含任何属性。 (如果是这样,我不知道 TypeScript 允许你这样做......)typescript 版本 4.1.2
Playground
最佳答案
与普通类型相比,这与泛型类型(参见 PR )如何解决传播有关。如果将结果对象写入变量,您会立即注意到不同之处:对于非泛型类型,合并类型推断为:
{
profile: {};
userId: number;
}
这导致此类型无法分配给带注释的返回类型 User
有一个必需的 username
子属性。这正是编译器错误 TS 2322 告诉您的:Property 'username' is missing in type '{}' but required in type '{ username: string; }'
现在,泛型的情况有点不同:类型实际上被推断为
User
的子类型的交集。和一个 { profile: {}; }
类型:T & {
userId: string;
profile: {};
}
编译器对此没问题,因为交集是带注释的返回类型的“扩展”,其中包含根据交集定义的所有属性。这是否是一种良好的行为值得商榷,因为您可以执行以下操作,编译器也不会更明智:
function updateUsernameGeneric<T extends User>(user: T): T {
const newUser = {
...user,
userId: "234",
profile: {
// No error (unexpected... why?)
}
}
return newUser;
}
updateUsernameGeneric({ profile: { username: "John" }, userId: 123 }).userId //a-ok, "number"
由于返回类型是泛型类型参数和合并属性的交集,您可以取消注释返回类型并让 TypeScript 推断它。不兼容的属性类型将被正确推断为
never
:function updateUsernameGenericFixed<T extends User>(user: T) {
const newUser = {
...user,
userId: "234",
profile: {
// No error (unexpected... why?)
}
}
return newUser;
}
updateUsernameGenericFixed({ profile: { username: "John" }, userId: 123 }).userId //never
Playground
关于typescript - 为什么类型保护类型的差价会导致类型检查被跳过?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66538678/