
示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载在 JavaScript 生态中option(key, value)链式调用再常见不过如 jQuery 的链式 API、各种 Builder 模式但切换到 TypeScript 后如何让每一级链式调用的返回值类型都精确可追踪却是一个考验泛型功底的中级关卡。本文以 type-challenges 仓库中的 第 12 题 Chainable Options 为主体完整拆解题目要求、解题思路、可运行的参考实现并结合仓库内的 template.ts 与 test-cases.ts 验证解法让你掌握用类型参数在链式调用中累积并返回状态的核心能力。一、题目背景当链式调用遇上 TypeScript原题描述见 README.ja.md对应中文版为 README.zh-CN.md指出JavaScript 中经常使用可串联Chainable / Pipeline的函数构造一个对象但在 TypeScript 中能否合理地给它赋上类型本挑战要求定义这样一个类型Interface、Type 或 Class 均可它提供两个方法option(key, value)使用给定的 key 和 value扩展当前 config 的类型get()获取最终累积的结果。题目特别强调只需要在类型层面实现不需要编写任何 JS/TS 运行时逻辑可以假设key只接受stringvalue可以是任意类型原样暴露即可无需额外处理同样的key只会被使用一次即输入保证无重复 key但好的解法通常会主动防御重复 key。题目给出的期望示例declare const config: Chainable const result config .option(foo, 123) .option(name, type-challenges) .option(bar, { value: Hello World }) .get() // 期望 result 的类型是 interface Result { foo: number name: string bar: { value: string } }可以看到链式调用三次option后get()必须返回一个包含foo: number、name: string、bar: { value: string }三个键的对象类型——类型必须记住每次调用传入的 key 与 value 类型。二、起始模板与测试用例明确你要达成的目标仓库为本题提供了初始模板 template.tstype Chainable { option(key: string, value: any): any get(): any }这个模板是类型层面完全退化为any的占位版本虽然能通过编译但get()返回any链式调用丢失了全部类型信息这正是需要被替换的部分。真正的验收标准在 test-cases.ts 中它定义了三个用例declare const a: Chainable const result1 a .option(foo, 123) .option(bar, { value: Hello World }) .option(name, type-challenges) .get() const result2 a .option(name, another name) // ts-expect-error .option(name, last name) .get() const result3 a .option(name, another name) // ts-expect-error .option(name, 123) .get() type cases [ ExpectAliketypeof result1, Expected1, ExpectAliketypeof result2, Expected2, ExpectAliketypeof result3, Expected3, ] type Expected1 { foo: number bar: { value: string } name: string } type Expected2 { name: string } type Expected3 { name: number }测试用例透露出三个隐藏要求累积性result1的三次option必须合并为一个包含三个键的对象类型Expected1重复 key 防御result2中对name连续赋值两次必须报错ts-expect-error标记option(name, last name)这一行意味着该行必须产生类型错误才能通过检查类型一致性result3中第二次对name传入number与第一次的string冲突也必须报错同时第一个option(name, another name)后若没有第二次冲突赋值结果类型应为{ name: number }Expected3。其中Alike和Expect等断言类型来自仓库的 utils/index.d.ts其自身也有完整的自测 utils/index.d.test.tsAlikeX, Y会先通过MergeInsertions递归展开对象、把交叉类型扁平化再用Equal做严格比较因此即使解法的累积结果形如A B C也能与字面对象类型Expected1判定相等。三、核心解法用泛型参数在类型层面累积状态解决本题的关键是让Chainable自身携带一个类型参数作为累积容器让option返回携带新状态的Chainable新实例。下面是推荐实现type ChainableO {} { optionK extends string, V( key: K extends keyof O ? never : K, value: V ): ChainableOmitO, K RecordK, V get(): O }逐点拆解这个解法的四个设计决策1. 泛型参数O作为当前配置类型的累积容器type ChainableO {}用默认参数{}作为初始状态。每次调用option后返回的ChainableOmitO, K RecordK, V把新键值对合并进旧状态实现了状态的传递与累积。2.K extends string约束 key 为字符串字面量当调用config.option(foo, 123)时TypeScript 会优先将foo推断为字符串字面量类型foo而非宽泛的string从而让RecordK, V能精确生成{ foo: number }这样的单键对象类型。3.K extends keyof O ? never : K主动拦截重复 key这是能否通过result2/result3两个ts-expect-error用例的关键。若K已经存在于累积状态O的键集合中则把参数类型收敛为never使重复的option(name, ...)调用产生类型错误。这一防御虽然题目声明相同 key 不会传两次却让解法更健壮、能覆盖测试用例的负向断言。4.OmitO, K RecordK, V完成类型合并RecordK, V生成{ [K]: V }OmitO, K先剔除旧状态中可能存在的同名键配合第 3 点虽不会触发但逻辑上更严谨二者交叉后得到旧状态 新键值对。get(): O则把累积结果整体返回。运行效果验证declare const config: Chainable const result config .option(foo, 123) // Chainable{ foo: number } .option(name, type-challenges) // Chainable{ foo: number; name: string } .option(bar, { value: Hello World }) // Chainable{ foo: number; name: string; bar: { value: string } } .get() // { foo: number; name: string; bar: { value: string } }result的类型与题目要求的Result接口完全一致也满足test-cases.ts中Expected1的Alike断言。四、备选思路与常见误区备选基于keyof的简单累积type ChainableO {} { optionK extends string, V(key: K, value: V): ChainableO RecordK, V get(): O }这个版本同样能通过result1的正向断言也能完成类型累积。但它的option对 key 没有任何重复约束result2中option(name, last name)不会产生类型错误导致ts-expect-error失效、测试无法通过。因此它更适合输入保证无重复 key的场景而本文推荐的K extends keyof O ? never : K版本能同时覆盖正负用例。常见误区一忽略泛型参数导致状态丢失沿用模板的option(key: string, value: any): any是典型的失败写法——返回any使Chainable无法携带状态。若把返回值写成Chainable不带参数每次option都会回到初始{}状态get()得到空对象类型。常见误区二混淆string与字符串字面量若约束写成key: stringfoo会被放宽为stringRecordstring, V会退化为索引签名类型无法精确到单键对象Alike断言也随之失败。必须用泛型K extends string保留字面量类型。常见误区三返回值类型不够精确get()若返回ChainableO[get]之外的类型例如直接返回O ...之外的组合或option返回不带O参数的Chainable都会让链式调用在第二级之后类型失真。五、围绕本题的类型体操知识点复习本题虽为中等难度但覆盖了后续大量题目如append-to-object、merge、diff等都依赖的类型工具泛型默认参数ChainableO {}为类型参数提供初值是状态累积类解法的通用底座字面量类型推断泛型约束K extends string保留 key 的字面量精度条件类型与keyofK extends keyof O ? never : K是拒绝已存在键的防御写法Record与Omit组合OmitO, K RecordK, V等价于替换或新增键的合并操作交叉类型的可比较性Alike基于MergeInsertions扁平化让A B与{ ... }能够直接比较见 utils/index.d.ts。如果希望进一步理解题目中option返回值推断、条件类型分支等细节仓库 guides 目录下的infer.md、key-in.md、recursive.md提供了对应主题的说明入口目前为 TODO 占位可通过仓库的 README.md 了解整体结构。六、如何运行与验证本题本题的运行验证依赖仓库根目录的 package.json 中的 TypeScript 类型检查流程。常规操作是在仓库根目录执行依赖安装与类型检查命令具体命令以package.json的scripts和 tsconfig.json 为准将test-cases.ts作为编译检查目标若类型断言Expect全部满足、ts-expect-error行均确实报错则代表解法通过全部用例。题目难度与作者信息记录在 info.yml 中medium / #application作者 Anthony Fu可作为解题进度的检索依据。小结Chainable Options 的核心思想并不复杂让类型像运行时状态一样在链式调用中传递——用O累积、用Record扩展、用get()收口再用条件类型防御重复键。掌握这一模式后无论是实现类型安全的 Builder、配置合并器还是阅读框架中大量链式 API 的类型定义你都能快速看懂其状态传递的脉络。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐type-challenges 第 10 题Tuple to Union —— 用索引访问 泛型实现元组转联合类型type challenges 第 10 题Tuple to Union —— 用索引访问 泛型实现元组转联合类型 本篇指南聚焦 type challen示例工程AI4Animation 中的 AdamW 优化器与余弦退火重启调度器解耦权重衰减与循环学习率实战指南AI4Animation 中的 AdamW 优化器与余弦退火重启调度器解耦权重衰减与循环学习率实战指南 本文围绕 AI4Animation https://l示例工程type-challenges 第 8 题 MyReadonly2实现「对象部分属性只读」的进阶泛型type challenges 第 8 题 MyReadonly2实现「对象部分属性只读」的进阶泛型 本篇文章以 questions/00008 medium示例工程上一篇Codex Security 攻击路径分析实战指南从 source 到 sink 的证据链构建与严重性校准下一篇refine 实时搜索与过滤入门指南从搜索框到服务端过滤创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考