TypeScript中表示任意对象的正确方式及keys函数选型探讨
你封装的keys函数核心是为了规避重复的类型断言,让Object.keys直接返回keyof O类型的数组。下面逐一分析你写的四种泛型约束的差异,并给出最优方案:
1. function keys<Key extends string | symbol | number, O extends Record<Key, unknown>>(object: O)
这种写法虽然可行,但存在冗余的泛型参数:Key会被自动推导为keyof O,额外声明它完全没必要。Record<Key, unknown>的作用是描述“键类型为Key、值类型为unknown的对象”,但O本身已经是具体的对象类型,这个约束并没有带来额外的类型安全性,反而增加了代码复杂度。
2. function keys<O extends Object>(object: O)
Object是TS中最宽泛的对象类型,它包含所有对象(普通对象、数组、函数、包装对象等),仅约束值不是原始类型。但它的语义偏向“具备所有对象通用方法的类型”(比如toString、valueOf),属于较旧的类型定义,TS官方现在更推荐使用语义更明确的object类型。
3. function keys<O extends {}>(object: O)
{}是空对象类型,语义上容易被误解为“只能传入无自身属性的对象”,但实际TS允许任何非原始值(包括带属性的对象)赋值给它。这种类型的语义和实际行为存在偏差,容易造成代码阅读者的误解,不推荐使用。
4. function keys<O extends object>(object: O)
object是TS2.2引入的类型,专门用来表示所有非原始值(普通对象、数组、函数、正则等),语义非常明确:“只接受非原始类型的值”。同时,泛型推导能完整保留O的具体属性信息,不会丢失类型细节,完全符合你封装函数的需求,是TS官方推荐的表示任意对象的类型。
最优方案
直接使用第四种写法即可:
function keys<O extends object>(object: O) { return Object.keys(object) as Array<keyof O> }
补充说明:TS官方将Object.keys的返回值定义为string[],是因为JS中对象的键最终都会被转为字符串(Symbol键不会被Object.keys返回),且无法保证运行时对象的键和编译时keyof O完全一致(比如对象可能有额外的隐式属性)。但在你的业务场景中,如果你能确保对象的键与编译时类型完全匹配,使用as Array<keyof O>是安全且合理的类型断言。
内容的提问来源于stack exchange,提问作者opensas

