如何无需类型断言将unknown收窄为Record<PropertyKey, unknown>
问题场景
尝试在不使用类型断言或自定义类型守卫的前提下,将unknown类型变量收窄为Record<PropertyKey, unknown>时,遇到如下类型错误:
Type 'object' is not assignable to type 'Record<PropertyKey, unknown>'.
Index signature for type 'string' is missing in type '{}'.ts(2322)
当前代码如下:
const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => { if ( typeof variable !== "object" || Array.isArray(variable) || variable === null ) { throw new Error("Not Record"); } return variable };
我的疑问:
- 如何改进代码满足要求,不用类型断言或自定义守卫?
- 若要支持解析空对象,是否需要将返回类型改为
Record<PropertyKey, unknown> | {}?毕竟unknown[]可以同时覆盖空数组和非空数组。
解决方案
核心问题原因
TypeScript中,typeof variable === 'object'收窄后的类型是object,这个类型包含空对象{},但Record<PropertyKey, unknown>要求对象必须支持任意string/number/symbol类型的键访问(即带有索引签名),空对象{}不具备这个特性,因此直接返回会触发类型错误。
无断言/自定义守卫的改进方案
可以利用Object.assign的类型推断特性,让编译器自动识别返回值符合Record<PropertyKey, unknown>类型:
const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => { if (typeof variable !== "object" || Array.isArray(variable) || variable === null) { throw new Error("Not Record"); } // 通过Object.assign将对象转换为带索引签名的类型 return Object.assign({}, variable); };
原理:Object.assign的第一个参数是空对象字面量{},TypeScript会推断其返回类型为Record<PropertyKey, unknown>,合并传入的variable后,最终返回值的类型会被编译器认可。
关于空对象的支持
不需要将返回类型改为Record<PropertyKey, unknown> | {}。因为通过Object.assign处理后,空对象{}会被转换为Record<PropertyKey, unknown>类型(空对象本身也符合该类型的定义:允许任意键,值为unknown只是没有实际属性),因此当前签名已经能覆盖空对象的场景。
内容的提问来源于stack exchange,提问作者myol

