尧图精选

TypeScript 类型挑战 ReplaceKeys 深度解析:批量替换联合类型成员键与值类型(type-challenges 1130)

🕒 发布时间:2026/10/2 7:55:26 📁 来源:尧图网络
示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载导读ReplaceKeys是 type-challengesTypeScript 类型挑战题库中的第 1130 道中等难度题目主题标签为object-keys由社区作者 lullabyjune 提出见 questions/01130-medium-replacekeys/info.yml。它要求你在纯类型层面实现一个键替换器给定一个联合类型、一组要替换的键名、以及一张新类型对照表为联合类型中的每个成员对象替换指定键的值类型如果某个成员根本没有这个键就跳过它。读完本文你将掌握映射类型 条件类型 分发的组合用法理解联合类型在键维度上逐成员加工的底层机制并能够独立写出通过官方测试用例的实现。题目回顾ReplaceKeys 的核心要求原题英文版 与 韩文版的描述可归纳为两点规则替换规则实现一个接收三个类型参数的ReplaceKeysU, T, Y其中U是目标联合类型T是要替换的键名键名联合Y是提供新类型的对象类型。跳过规则如果联合类型中的某个成员不包含T中的某个键则该成员保持原样不做替换。官方给出的演示数据如下必须完整保留它是理解本体的最好素材type NodeA { type: A name: string flag: number } type NodeB { type: B id: number flag: number } type NodeC { type: C name: string flag: number } type Nodes NodeA | NodeB | NodeC场景一键存在正常替换type ReplacedNodes ReplaceKeys Nodes, name | flag, { name: number; flag: string } // {type: A, name: number, flag: string} | {type: B, id: number, flag: string} | {type: C, name: number, flag: string} // 把 name 从 string 替换为 number把 flag 从 number 替换为 string。注意NodeB本来没有name键所以它的name不会被凭空造出来而它有的flag则被正常替换。这正是跳过规则的体现。场景二键在成员中存在、但在对照表中不存在type ReplacedNotExistKeys ReplaceKeysNodes, name, { aa: number } // {type: A, name: never, flag: number} | NodeB | {type: C, name: never, flag: number} // 把 name 替换为 never。这里揭示了一个容易被忽略的隐含行为当T中的键在成员对象里存在、但Y里没有对应条目时结果不是保持原类型而是被替换为never。同时NodeB因为根本没有name键被原样保留NodeB这个成员类型甚至不需要展开。拆解三个泛型参数U、T、Y 的职责参数名称约定职责取值形态UUnion联合类型被加工的对象联合输出仍是同样数量成员的联合对象类型的联合如NodeA \| NodeB \| NodeCTTarget keys目标键声明哪些键要被替换键名的联合如name \| flagYNew types新类型表为每个目标键提供新值类型对象类型如{ name: number; flag: string }三者组合出三种分支构成整个题目的逻辑核心键P不在T中P与原始类型原样保留type、id等非目标键不受影响。键P在T中、且在Y中值类型被替换为Y[P]。键P在T中、但不在Y中值类型被替换为never。前置知识三个关键类型机制ReplaceKeys的优雅之处在于它只用三条 TypeScript 类型语法却完整实现了上述三分支逻辑。1. 映射类型Mapped Type——逐键重写对象{ [P in keyof U]: ... }会遍历U的每个键并允许对每个键产出新的类型。这是重写对象结构的基础设施。值得注意的是当U是联合类型时TypeScript 的映射类型会对联合的每个成员分别执行映射分发行为这正是联合进、联合出的关键。从仓库源码结构看这也是题目将其定位为中等难度的原因之一——它要求你同时掌握映射类型与联合分发的交互。2. 条件类型与分发Conditional Type Distributivity裸类型参数上的条件类型P extends T ? A : B会在P、T为联合时把联合拆开逐个判断。在ReplaceKeys中keyof U得到的是联合成员的键的并集条件P extends T负责筛选是否为目标键。3. 索引访问Indexed AccessY[P]从对照表中取出目标键对应的新类型。由于P extends T成立时并不能自动推导P extends keyof Y需要再嵌套一层P extends keyof Y ? Y[P] : never来安全取值——这一层同时也正好实现了对照表里没有该键就输出never的隐含规则。从仓库源码看测试约束题目仓库提供了三个直接相关的文件是实现前最权威的验收标准template.ts起始模板只有一个占位声明type ReplaceKeysU, T, Y any你的任务就是把它替换成真实实现。test-cases.ts官方测试用例本文下面会逐条解读。utils/index.d.ts测试基础设施其中EqualX, Y使用函数逆变技巧做精确类型相等比较ExpectT extends true T负责让不通过的类型断言直接编译报错。测试用例拆解源码内容如下type ReplacedNodeA { type: A; name: number; flag: string } type ReplacedNodeB { type: B; id: number; flag: string } type ReplacedNodeC { type: C; name: number; flag: string } type NoNameNodeA { type: A; flag: number; name: never } type NoNameNodeC { type: C; flag: number; name: never } type Nodes NodeA | NodeB | NodeC type ReplacedNodes ReplacedNodeA | ReplacedNodeB | ReplacedNodeC type NodesNoName NoNameNodeA | NoNameNodeC | NodeB type cases [ ExpectEqualReplaceKeysNodes, name | flag, { name: number, flag: string }, ReplacedNodes, ExpectEqualReplaceKeysNodes, name, { aa: number }, NodesNoName, ]两条断言分别锁定了两条规则ReplaceKeysNodes, name | flag, { name: number; flag: string }必须精确等于ReplacedNodeA | ReplacedNodeB | ReplacedNodeC。即三个成员都被重写NodeA、NodeC的name变成number三个成员的flag都变成string而NodeB没有的name不会被添加。ReplaceKeysNodes, name, { aa: number }必须精确等于NoNameNodeA | NoNameNodeC | NodeB。注意NoNameNodeA中name的类型被明确写成了never且属性顺序变为type、flag、name——Equal是精确比较说明实现不仅类型要对连映射后键的顺序也需要与预期一致这正是逐成员重新映射的天然结果。NodeB因不含name被原样保留连展开都不需要。这两条测试覆盖了题目描述中替换成功与对照表缺失键两种极端是验证实现的黄金标准。实现逐层递进到最终答案第一步先写一个不考虑分发的朴素版本type ReplaceKeysU, T, Y { [P in keyof U]: P extends T ? (P extends keyof Y ? Y[P] : never) : U[P] }逐键逻辑为P extends T判断该键是否属于目标键集合若属于再判断P extends keyof Y能取到新类型就取Y[P]取不到就输出never若不属于保留原始类型U[P]。第二步验证联合分发是否天然成立由于 TypeScript 的映射类型对联合类型U会逐成员执行等价于联合的每个成员各自完成一次映射再拼回联合上述写法对Nodes会分别作用于NodeA、NodeB、NodeC从而得到三成员各自重写、缺失键天然跳过的结果。它是能通过测试的最短实现也是社区中最常见的解法形态。第三步可选显式分发更稳更清晰如果你希望完全不依赖映射类型对联合的隐式分发可以手动用条件类型强制分发type ReplaceKeysU, T, Y U extends unknown ? { [P in keyof U]: P extends T ? (P extends keyof Y ? Y[P] : never) : U[P] } : neverU extends unknown ? ... : never这个恒真条件类型会把联合拆成单个成员逐个处理再把结果重新合并为联合语义更直白。两种写法对本题输出等价建议先理解隐式版本再对比显式版本从而彻底掌握两种分发触发方式。第四步边界推演T为空neverP extends never恒不成立所有键原样保留输出等于输入。Y中没有目标键命中第三分支输出never这正是场景二的行为。U中同时存在含目标键与不含目标键的成员含的成员被重写不含的成员原样保留两者仍组成一个联合。验证与本地运行本项目支持把全部题目在本地以 playground 形式跑起来见 README.md 的 Play Locally 一节先确保安装最新版 Node.js 与 pnpm然后pnpm install pnpm generate脚本会提示选择语言之后在./playground目录即可用你熟悉的 IDE 打开题目并实时看到test-cases.ts的类型检查结果。若想保留已有改动再更新 playground可执行pnpm generate --keep-changes或简写pnpm generate -K。在严格模式项目所有题目均在strict下运行下ExpectEqual...断言一旦不满足TypeScript 编译器会直接报错因此零报错就是实现通过的判据。小结ReplaceKeys是 type-challenges 中一道以小见大的中等题它不涉及递归、不涉及模板字面量却精准地考察了映射类型、条件类型的分发、索引访问与never语义四者的协同。透过 test-cases.ts 的两条断言你可以看到类型编程里精确相等的验收方式而整个题库也正是通过这样一个个小而实的题目帮助开发者从会写业务类型进阶到能设计工具类型。把它和同标签object-keys下的 Pick、Readonly、Deep Readonly、Mutable 等题目放在一起练习能系统性地建立起对象键操作的类型直觉。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐掌握Type Challenges中的ReplaceKeys类型提升TypeScript高级类型技巧的完整指南掌握Type Challenges中的ReplaceKeys类型提升TypeScript高级类型技巧的完整指南 Type Challenges是一个专注于提升示例工程TypeScript 类型体操type-challenges 599 题 Merge 类型合并深度解析TypeScript 类型体操type challenges 599 题 Merge 类型合并深度解析 type challenges 的 599 题 Mer示例工程TypeScript 类型挑战type-challenges 题 112 之 CapitalizeWords 模板字面量类型实战解析TypeScript 类型挑战type challenges 题 112 之 CapitalizeWords 模板字面量类型实战解析 本文以 question示例工程上一篇最完整Django多租户方案用PostgreSQL模式实现SaaS系统隔离下一篇NetBox API Token 完全指南v1/v2 双版本机制、字段详解与安全实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →