You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何经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为只读属性),但数字是否也有类似只读属性?


原因解析

  1. 非同态映射类型的核心影响
    你的DeepWriteable因为使用了-readonly修饰符,属于非同态映射类型。这类映射不会保留原始类型的继承关系和身份特征,而是基于keyof T生成全新的类型结构。对于普通基础类型(number/string),TypeScript有内部兼容处理,会直接返回原始类型;但品牌化类型是基础类型 & 品牌对象的交叉类型,映射后会生成纯对象结构,丢失了和基础类型的交叉关系。

  2. 基础类型与品牌化类型的keyof差异

  • 普通number/string的keyof会返回它们原型链上的方法和属性(比如number的toString、toFixed,string的length、charAt),但TypeScript会识别出映射后的类型和原始基础类型兼容,所以DeepWriteable<number>最终还是number。
  • 品牌化类型(如PositiveNumber)的keyof会包含基础类型的所有属性加上自定义的__brand属性。DeepWriteable会遍历这些属性并移除readonly,生成一个包含所有这些属性的对象类型。这个新对象类型不再是number & { __brand: ... }的交叉类型,自然无法继承number。
  1. 数字类型的属性细节
    number的原型属性(比如toPrecision、valueOf)本身并非readonly,但-readonly操作的存在已经触发了非同态映射的逻辑,TypeScript不会再将映射后的类型视为原始number的子类型,而是作为独立的对象类型处理,这就是为什么DeepWriteable<PositiveNumber>不再extendsnumber。

内容的提问来源于stack exchange,提问作者Conor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 02:10:19