interface Student {
id: number
name: string
age: number
}
let alice: Student = { name: "alice", age: 19, id: 1 }
const bob: Student = { name: "Bob", age: 19, id: 2 }
const keys: (keyof Student)[] = ["age", "name"]
for (let c of keys) {
alice[c] = bob[c] // here shows the error during compile
/*
src/index.ts:11:3 - error TS2322: Type 'string | number' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
*/
}
如何编辑代码才不会出现打字错误
alice[c] = bob[c]
?
目前,当您编写
alice[c] = bob[c]
形式的代码时,编译器仅分析键的类型,而不分析它们的身份。这意味着它无法区分 alice[c] = bob[c]
和 alice[c1] = bob[c2]
之间的区别,其中 c1
和 c2
与 c
都是同一类型。 人们可能会认为这很好,并且分配的双方都是索引访问类型Student[keyof Student]
。
但是当键是像keyof Student
这样的联合类型时,TypeScript 会以不同的方式对待索引访问
写入。这个安全功能是在 microsoft/TypeScript#30769 中实现的,它确实可以捕获合法的问题,因为
alice[c1] = alice[c2]
确实不安全;例如,如果 c1
是 "id"
但 c2
是 "name"
会怎么样?
但它也禁止一些完全安全的代码。 对于
alice[c] = bob[c]
,我们知道每一侧的c
不仅仅是相同的类型,而且是相同的值。 在 microsoft/TypeScript#32693 有一个支持这种“同键”分配的功能请求,但尚未实现。
除非实现,否则您需要重构到编译器将赋值的两侧视为相同或兼容的版本。 根据 对 microsoft/TypeScript#30769 的评论,如果
k
是 generic 类型,你可以得到这个。
因此,这里最简单的方法是将赋值放在泛型函数中...而最简单的方法是从 for...of
循环
更改为
forEach()
数组方法,其中接受回调:
keys.forEach(<K extends keyof Student>(c: K) => {
alice[c] = bob[c]
});
现在
c
是泛型类型
K
,赋值的两边都被视为泛型索引访问类型 Student[K]
,这是允许的。Playground 代码链接
c
循环中的
for
具有类型keyof Student
,即可以是Student
的任意键,因此alice[c]
和bob[c]
的类型是Student
中所有属性类型
的并集,即string | number
。
问题是,当你尝试做作业
alice[c] = bob[c]
时,TS不够聪明,无法知道alice[c]
和
bob[c]
必须是同一类型:TS只知道
string | number
,然后右侧的类型,但在左侧变为(等于)
string & number
(并集变为交集!),进而简化为
never
。 错误从何而来。
这是有效的代码,不一定适合您,具体取决于您实际需要的“通用”程度,但它说明了这样一个事实:如果所有属性都具有相同的类型,则分配会很好地工作,因此,类型保护使得在每个条件分支中都有一个已知的唯一属性类型足以完成这个任务:
interface Student {
id: number
name: string
age: number
}
let alice: Student = { name: "alice", age: 19, id: 1 };
const bob: Student = { name: "Bob", age: 19, id: 2 };
declare const keys: (keyof Student)[];
// to show that results do not depend on the specific value of `keys`
for (const c of keys) {
if (c === "name") {
c; // const c: "name"
alice[c] = bob[c]; // no error
}
else {
c; // const c: "id" | "age"
alice[c] = bob[c]; // no error
}
c; // const c: keyof Student
const a_ = alice[c]; // const a_: string | number
const b_ = bob[c]; // const b_: string | number
alice[c] = bob[c]; // ERROR
// Type 'string | number' is not assignable to type 'never'.
}
type TEST = string & number; // type TEST = never
游乐场链接我认为帮助 TypeScript 检测带有
Object.assign()
将它们复制到 alice
。这个问题回答了如何创建仅包含键子集的正确类型的对象:如何获取 javascript 对象属性的子集
我稍微简化了该问题中pick()
的实现,以获得以下简单干净的解决方案:
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) =>
Object.fromEntries(keys.map(key => [key, obj[key]])) as Pick<T, K>;
interface Student {
id: number
name: string
age: number
}
let alice: Student = { name: "alice", age: 19, id: 1 };
const bob: Student = { name: "Bob", age: 19, id: 2 };
const keys: (keyof Student)[] = ["age", "name"];
Object.assign(alice, pick(bob, ...keys));
游乐场链接