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

TypeScript 4.5+中MyReadonly2交叉类型实现失效原因排查

问题背景

我在完成type-challenges中的类型题目,题目要求实现接收两个类型参数T、K的泛型MyReadonly2<T, K>,规则如下:

  • K用于指定T中需要设置为只读的属性集合
  • 若未传入K,则需要将T的所有属性设为只读,与内置Readonly类型的行为一致

我编写的第一版方案在TypeScript 4.4版本可正常运行,但在4.5及以上版本中无法正常工作,代码如下:

//solution 1
type MyReadonly2<T, K extends keyof T = keyof T> = T & {
  readonly [P in K]: T[P]
}

我找到的修复方案可在v4.7.2版本正常运行,代码如下:

// solution 2
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & {
  readonly [P in K]: T[P]
}

我始终无法理解第一种实现方案失效的根本原因,需要技术层面的解答。


失效原因说明

TypeScript 4.5 交叉类型修饰符合并规则变更

第一版方案失效的核心原因是TypeScript 4.5对交叉类型的属性修饰符(包括readonly、可选修饰符?)合并逻辑做了破坏性更新:

  • 4.4及更早版本中,交叉类型内如果同名属性出现在多个成员类型中,后定义的成员的属性修饰符会覆盖先定义的。第一版写法中,原始类型T的K集合属性默认是可写状态,后面交叉的映射类型给K集合属性加了readonly修饰符,按照旧规则后者会覆盖前者,最终输出符合预期。
  • 4.5及之后版本,TS调整了合并优先级:直接来自原始类型的属性,其自带的修饰符优先级高于交叉时追加的其他同名属性的修饰符。第一版直接交叉原始类型T,K集合属性在原始T中是可写的,后续追加的readonly修饰符会被TS直接忽略,自然无法实现只读效果。

修复方案生效逻辑

第二版方案通过Omit<T, K>先把要修改修饰符的K集合属性从原始T中完全剔除,再交叉追加了readonly修饰符的K属性映射:

  • 交叉的两个成员不存在同名属性:Omit<T, K>仅保留T中不在K范围内的属性,原有修饰符完全不变
  • 后续的映射类型仅包含K范围内的属性,不存在和原始T的同名属性修饰符冲突,readonly修饰符可以被正确识别保留,最终输出符合预期。

注意:这次规则变更是TS为了修复此前交叉类型修饰符合并逻辑不一致的一系列问题推出的,从4.5版本开始,直接交叉原始类型追加修饰符的写法不再可靠。所有需要修改原类型部分属性修饰符的场景,都应该先通过Omit等工具类型把待修改属性从原类型中移除,再交叉修改完修饰符的对应属性集合。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 04:24:12