为何经DeepWriteable处理的品牌化字符串/数字类型不再继承原类型?
问题:品牌化类型经过DeepWriteable转换后不再继承原始类型的原因?
我定义了DeepWriteable类型以及品牌化数字类型PositiveNumber。PositiveNumber继承自number,但DeepWriteable<PositiveNumber>却不再继承number;品牌化字符串类型也存在相同情况。具体代码示例如下:
type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> }; type PositiveNumber = number & { __brand: 'PositiveNumber' }; type NonEmptyString = string & { __brand: 'NonEmptyString' }; type test1 = PositiveNumber extends number ? true : false; // TRUE type test2 = DeepWriteable<PositiveNumber> extends number ? true : false; // FALSE type test3 = NonEmptyString extends string ? true : false; // TRUE type test4 = DeepWriteable<NonEmptyString> extends string ? true : false; // FALSE
注:仅品牌化类型存在此问题,普通number和string无该现象:
type test5 = DeepWriteable<number> extends number ? true : false; // TRUE type test6 = DeepWriteable<string> extends string ? true : false; // TRUE
我已通过给DeepWriteable添加判断跳过数字和字符串的映射解决问题,但好奇背后原因。是否是-readonly转换导致该类型为非同态映射类型?我能理解字符串的情况(因length为只读属性),但数字是否也有类似只读属性?
原因解析
非同态映射类型的核心影响
你的DeepWriteable因为使用了-readonly修饰符,属于非同态映射类型。这类映射不会保留原始类型的继承关系和身份特征,而是基于keyof T生成全新的类型结构。对于普通基础类型(number/string),TypeScript有内部兼容处理,会直接返回原始类型;但品牌化类型是基础类型 & 品牌对象的交叉类型,映射后会生成纯对象结构,丢失了和基础类型的交叉关系。基础类型与品牌化类型的
keyof差异
- 普通
number/string的keyof会返回它们原型链上的方法和属性(比如number的toString、toFixed,string的length、charAt),但TypeScript会识别出映射后的类型和原始基础类型兼容,所以DeepWriteable<number>最终还是number。 - 品牌化类型(如
PositiveNumber)的keyof会包含基础类型的所有属性加上自定义的__brand属性。DeepWriteable会遍历这些属性并移除readonly,生成一个包含所有这些属性的对象类型。这个新对象类型不再是number & { __brand: ... }的交叉类型,自然无法继承number。
- 数字类型的属性细节
number的原型属性(比如toPrecision、valueOf)本身并非readonly,但-readonly操作的存在已经触发了非同态映射的逻辑,TypeScript不会再将映射后的类型视为原始number的子类型,而是作为独立的对象类型处理,这就是为什么DeepWriteable<PositiveNumber>不再extendsnumber。
内容的提问来源于stack exchange,提问作者Conor
相关产品推荐
相关产品推荐

