为何TypeScript无法在赋值时收窄对象字段类型?
TypeScript对象部分字符串字段更新的类型错误问题及解决方法
问题场景
以下代码尝试更新对象中指定的字符串类型字段,但TypeScript编译器在赋值时报错:
interface Obj { a: string; b: string; d: Date; } const fields: (keyof Obj)[] = ['a', 'b']; const obj: Obj = { a: 'c', b: 'd', d: new Date(), } fields.forEach((field) => { if (typeof obj[field] === 'string') { obj[field] = field; // 此处触发类型错误 } });
报错信息
Type 'string' is not assignable to type 'string & Date'.
Type 'string' is not assignable to type 'string & Date'.
Type 'string' is not assignable to type 'Date'.(2322)
报错原因
TypeScript无法通过typeof obj[field] === 'string'这个运行时检查,推断出field对应的属性类型一定是string。因为fields的类型被声明为(keyof Obj)[],编译器会认为循环中的field可能是Obj的任意键(包括类型为Date的d)。即便你手动传入的是'a'和'b',编译器也无法追踪数组元素与属性类型的关联,导致它将obj[field]的类型推断为string | Date。而条件判断后,编译器没能正确收窄类型,反而错误地生成了string & Date这个不可能的交集类型,最终引发赋值不匹配的错误。
解决方法
方法1:类型断言
直接通过类型断言告诉编译器当前属性的类型为string:
fields.forEach((field) => { if (typeof obj[field] === 'string') { (obj[field] as string) = field; } });
方法2:精准定义字符串键类型
先提取Obj中值类型为string的键,再将fields的类型指定为该键集合的数组,让编译器明确知道这些键对应的属性都是字符串:
interface Obj { a: string; b: string; d: Date; } // 提取Obj中值为string类型的键 type StringKeysOfObj = { [K in keyof Obj]: Obj[K] extends string ? K : never }[keyof Obj]; const fields: StringKeysOfObj[] = ['a', 'b']; const obj: Obj = { a: 'c', b: 'd', d: new Date(), } fields.forEach((field) => { obj[field] = field; // 无需额外判断,编译器已确认属性为string类型 });
方法3:自定义类型守卫
编写类型守卫函数,帮助编译器正确收窄field的类型:
interface Obj { a: string; b: string; d: Date; } const fields: (keyof Obj)[] = ['a', 'b']; const obj: Obj = { a: 'c', b: 'd', d: new Date(), } // 类型守卫:判断field对应的属性是否为string类型 function isStringField(obj: Obj, field: keyof Obj): field is 'a' | 'b' { return typeof obj[field] === 'string'; } fields.forEach((field) => { if (isStringField(obj, field)) { obj[field] = field; } });
内容的提问来源于stack exchange,提问作者Forseti
相关产品推荐
相关产品推荐

