type PersonType = typeof person
function cloneObj(obj: PersonType) {
const newObj = {} as PersonType
for (const key of Object.keys(obj)) {
// 报错 为啥会有never类型
// 类型"string|number"不可分配给类型"never"。
// 类型"string"不可分配给类型"never"。
newObj[key as keyof PersonType] = obj[key as keyof PersonType]
}
return newObj
}
const person = {
name: '曜',
sex: '男',
age: 16 // 如果属性值都是字符串就不报错
}
cloneObj(person)
挺有意思的一个问题。
首先要指出,题主这种写法,在 TS 3.4 之前是不会报错的。而在 TS 3.5 之后引入了一项名为 Fixes to Unsound Writes to Indexed Access Types 的破坏性变更后,才会报错。
相关的讨论有很多,比如 #30769、#33834。
那么为什么 TS 要把这种写法视为一种错误呢?
其实如果你只是取值(即 Read),是没问题的:
此时
v
的类型会推断为string | number
,也就是PersonType
中所有属性的联合类型。问题出现在赋值(即 Write)上。
我们可以看出,其实
PersonType
上每个属性的类型,是确定的,要么是string
、要么是number
,并不真的存在一个属性的类型是string | number
。因此我们在尝试下面的写法时,才会得到错误,这是符合预期的:但如果你用了索引属性去赋值,那么就会出现:
就打破了上面这种预期,也就是所谓的 Unsound Writes。
因此 TS 3.5 之后引入了这项破坏性变化,当你尝试这么做的时候,会把类型收窄为所有属性的交叉类型,只有满足此交叉类型的,才能正确赋值。具体到本题中得到的交叉类型也就是
string & number
,但很显然,没有任何类型T
是能满足T extends string & number
的,因此得到了never
,故而抛出题中的异常。改法有很多种,但我只推荐用泛型。
除非你这个
cloneObj
专门针对PersonType
编写的,否则我建议你这么写: