为何Record<string, never>会extends仅含可选属性的对象类型?
为什么
Record<string, never>会继承仅包含可选属性的对象类型? 我写了一个判断类型是否无属性的工具类型IsEmpty:
// 错误判断T是否为空类型 type IsEmpty<T> = Record<string, never> extends T ? true : false
但测试仅含可选属性的对象类型时,判断结果不符合预期:
type MyMixedType = { a: number, b?: number, } // 仅选取必填属性,不为空,判断结果为false(符合预期) type testA = IsEmpty<Pick<MyMixedType, 'a'>> // 仅选取可选属性,本不应为空,但判断结果为true(不符合预期) type testB = IsEmpty<Pick<MyMixedType, 'b'>>
我的问题是:为什么Record<string, never> extends {b?: number}会成立?
原因解析
这本质是TypeScript结构类型系统的兼容性规则导致的:
对象类型兼容性的核心逻辑:当判断
X extends Y时,TypeScript会检查X的所有实例是否都能被安全赋值给Y类型的变量——简单说,Y的所有要求,X都必须满足。Record<string, never>的含义:这个类型表示所有字符串键对应的属性值都是never类型。never是TypeScript中最底层的类型,它没有任何可能的取值,因此Record<string, never>的实例只能是完全没有任何属性的对象(因为任何属性都无法被赋值)。可选属性的兼容性规则:对于
{b?: number}这类包含可选属性的类型,它的实例有两种合法情况:- 没有
b属性的空对象; - 有
b属性且值为number的对象。
- 没有
两者的兼容性匹配:
Record<string, never>的实例(空对象)完全符合{b?: number}的要求(允许没有b属性);- 同时,
Record<string, never>中b属性的类型是never,而never是任何类型的子类型(因为不存在违反类型约束的可能),所以即使考虑“存在b属性”的情况,never也兼容number。
综上,Record<string, never>的所有可能实例都满足{b?: number}的类型约束,因此Record<string, never> extends {b?: number}返回true。
修正后的IsEmpty工具类型
如果要准确判断一个类型是否没有任何属性(包括可选属性),可以通过判断keyof T是否为never来实现:
type IsEmpty<T> = keyof T extends never ? true : false;
测试验证:
type testA = IsEmpty<Pick<MyMixedType, 'a'>> // false(正确,存在必填属性a) type testB = IsEmpty<Pick<MyMixedType, 'b'>> // false(正确,存在可选属性b) type testC = IsEmpty<{}> // true(正确,空对象) type testD = IsEmpty<Record<string, never>> // true(正确)
内容的提问来源于stack exchange,提问作者Holzchopf
相关产品推荐
相关产品推荐

