type-challenges 中阶挑战解析:用 TypeScript 类型系统实现 Array.shift(Shift\<T\>)
发布时间:2026/10/2 1:46:41来源:尧图网络
示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载本篇技术指南以 type-challenges 仓库中的 3062・Shift 中阶题目为核心完整讲解如何用类型系统实现Array.shift的类型版本从题目要求、仓库测试基准出发逐步推导出基于infer与元组解构的多种解法并深入剖析空数组、单元素数组、unknown约束与 readonly 元组等边界场景。读完本文你将掌握元组Tuple上的条件类型分发、剩余元素推断Rest Inference等核心技巧能够举一反三地独立实现Pop、Push、Unshift等数组操作类型。题目解读为 Array.shift 实现类型层版本在 JavaScript 中Array.prototype.shift()会移除并返回数组的第一个元素使原数组长度减一。type-challenges 的这道中阶medium题目要求我们把这个行为搬到类型层面实现一个泛型ShiftT输入一个数组/元组类型T输出去掉首元素后的剩余部分。题目给出的唯一示例见 questions/03062-medium-shift/README.mdtype Result Shift[3, 2, 1] // [2, 1]该题目由 jiangshanjiangshanmeta 系列题目作者之一发起难度为中阶medium标签为#array。与它直接关联的是仓库中的 16・Pop——两题互为镜像Pop删除最后一个元素Shift删除第一个元素。仓库中的题目骨架与测试基准要正确解题首先看清仓库为这道题准备的起点和判定标准。起点模板questions/03062-medium-shift/template.ts只有一行占位实现type ShiftT any注意模板的T没有extends any[]之类的约束这意味着约束需要由解题者自己加上——测试用例恰恰对此提出了要求。判定标准questions/03062-medium-shift/test-cases.ts共 5 个用例import type { Equal, Expect } from type-challenges/utils type cases [ // ts-expect-error Shiftunknown, ExpectEqualShift[], [], ExpectEqualShift[1], [], ExpectEqualShift[3, 2, 1], [2, 1], ExpectEqualShift[a, b, c, d], [b, c, d], ]逐个拆解这些用例它们恰好覆盖了四类关键场景用例输入期望输出考察点Shiftunknownunknown应产生类型错误ts-expect-error必须为T加上数组约束Shift[]空元组[]空数组不崩溃、返回空元组Shift[1]单元素元组[]移除唯一元素后为空Shift[3, 2, 1]/Shift[a,b,c,d]多元素元组去掉首元素核心功能正确性其中Equal与Expect来自仓库工作区的type-challenges/utils包utils/index.d.tsexport type ExpectT extends true T export type EqualX, Y (T() T extends X ? 1 : 2) extends (T() T extends Y ? 1 : 2) ? true : falseEqualX, Y是一个对类型做“结构严格相等”比较的辅助类型利用函数签名在泛型推断下的行为判断两类型是否完全一致Expect则强制结果必须为字面量true。因此测试的本质是只有当你的Shift输出与期望类型严格相等时ExpectEqual...才编译通过。解法一infer 解构 剩余元组最直观的思路与运行时实现完全对应把元组拆成首元素 剩余部分然后返回剩余部分。这依赖 TypeScript 条件类型中的infer与 rest 元素模式匹配type ShiftT extends any[] T extends [infer First, ...infer Rest] ? Rest : []逐部分解释T extends any[]为T加上数组约束。这是必须的——否则Shiftunknown不会报错第一个用例ts-expect-error就会失效导致测试编译失败。[infer First, ...infer Rest]将T与至少含一个元素的元组模式匹配。匹配成功时infer First捕获首元素类型...infer Rest捕获剩余元素的元组类型。匹配成功走真分支返回Rest匹配失败即空元组或非元组走假分支返回[]。验证几个关键用例type R1 Shift[3, 2, 1] // [2, 1] type R2 Shift[a,b,c] // [b, c] type R3 Shift[1] // [] First 捕获 1Rest 推断为 [] type R4 Shift[] // [] [] 无法匹配 [infer F, ...infer R]空元组走假分支[]的原因在于[]与[infer First, ...infer Rest]模式匹配时rest 元素...infer Rest要求至少存在零个以上元素可捕获但类型系统不会把一个零长度的[]强行套进至少一个元素的模式——因此不匹配返回[]。这与测试用例Shift[]的期望完全一致。解法二省略首元素名称直取剩余部分如果不需要在结果中使用首元素可以不为它命名把infer只用于剩余部分type ShiftT extends unknown[] T extends [unknown, ...infer Rest] ? Rest : []与解法一的区别用unknown占位匹配任意类型的首元素避免出现未被使用的infer First变量仓库 tsconfig 开启了noUnusedParameters保持类型整洁也能减少 ESLint 告警见 tsconfig.base.json。T extends unknown[]与T extends any[]在此处作用等价均是对元组/数组的类型约束。两种写法在本题的测试基准下结果完全一致选择哪种取决于个人风格与后续是否复用首元素。边界情况与类型约束详解为什么必须拒绝unknown测试用例第一行// ts-expect-error Shiftunknown明确要求把Shiftunknown写进代码应当产生类型错误。ts-expect-error注释的作用是断言下一行存在类型错误若下一行没有报错则本注释本身报错。这意味着若你的ShiftT没有extends any[]约束照抄模板type ShiftT anyShiftunknown不会报错ts-expect-error反而会触发无错误可断言的编译错误——测试直接失败加上约束后unknown不满足unknown extends any[]于是Shiftunknown产生类型 unknown 不满足约束 any[] 的错误ts-expect-error断言成立。单元素与空数组Shift[1]→[]模式[infer F, ...infer R]成功匹配R推断为空元组[]。Shift[]→[]模式匹配失败走假分支。注意两条路径汇合到同一个结果[]但语义不同——前者是移除唯一元素后自然为空后者是空元组本就没有首元素可移除。readonly 元组的行为可推断结论仓库测试未显式覆盖 readonly 元组但从模式匹配机制可以推断Shiftreadonly [1, 2]这类输入在[infer First, ...infer Rest]匹配时会保留 readonly 修饰——infer推断出的Rest会维持元组的只读性readonly [2]。若希望强制输出普通可变元组可在返回前用[...Rest]展开例如T extends [unknown, ...infer Rest] ? [...Rest] : [][...Rest]会剥离 readonly 修饰生成新的可变数组类型。这一写法同样能通过既有全部用例。与 Pop、Push、Unshift 的对照数组操作四件套Shift并非孤立的题目。仓库将数组增删操作拆成了难度递进的系列16・Pop中阶删除最后一个元素。其模板为type PopT extends any[] any典型的解法是把模式写成[...infer Rest, infer Last]——rest 元素放在前面捕获剩余部分最后一个元素由infer Last捕获。其测试用例同样覆盖了空数组Pop[]返回[]的情况见 questions/00016-medium-pop/test-cases.ts。题目描述中还专门提示作为额外练习能否顺便实现Shift、Push与Unshift3057・Push简单在元组末尾追加一个元素模板type PushT, U any典型实现为[...T, U]。3060・Unshift简单在元组开头插入一个元素模板type UnshiftT, U any典型实现为[U, ...T]。四个类型可以横向对比类型操作运行时原型核心模式匹配难度ShiftTArray.shift()T extends [infer F, ...infer R] ? R : []mediumPopTArray.pop()T extends [...infer R, infer L] ? R : []mediumPushT, UArray.push()[...T, U]easyUnshiftT, UArray.unshift()[U, ...T]easy可以看到一个规律凡是删头去尾的操作都需要infer 条件类型因为结果剩余部分是被推断出来的而追加插入只需要 rest 展开语法因为结果是直接构造出来的。这也是为什么Push/Unshift是 easy、而Pop/Shift是 medium。此外同属数组主题的还有 14・First of Array仅提取首元素模板为type FirstT extends any[] any实现T extends [infer F, ...any[]] ? F : never与 15・Last of Array它们可以看作Shift/Pop的只读版本前置练习。原理深入infer 在元组上的工作方式条件类型与模式匹配T extends [infer First, ...infer Rest] ? Rest : []本质是 TypeScript 条件类型T extends X ? A : B的一种应用当T与模式X结构兼容时进入真分支并在该分支内使用infer声明的类型变量。对元组而言匹配是按位置进行的普通元素位置如infer First捕获该位置的元素类型rest 位置...infer Rest捕获剩余元素组成的元组类型。这正是元组级 shift得以实现的核心机制rest 元素推断天然产生一个与剩余元素一一对应的元组无需任何手工展开。空元组为何不匹配模式[infer First, ...infer Rest]蕴含第一个位置必须有一个元素。空元组[]不满足这一结构约束因此[] extends [infer First, ...infer Rest]为 false条件类型落入假分支返回[]。这与运行时的防御性处理思路一致shift()对空数组返回undefined且不改变数组类型层面则原样返回空元组避免产生never或越界访问。在本仓库中验证实现type-challenges 没有单独的 test 脚本见根目录 package.json判题方式是把 questions/03062-medium-shift/test-cases.ts 与你的实现放在一起做编译期校验将Shift的实现填入 questions/03062-medium-shift/template.ts仓库已通过pnpm-workspace.yaml将type-challenges/utils挂为工作区依赖声明见根 package.json 的type-challenges/utils: workspace:*执行pnpm install后即可获得Equal/Expect运行 TypeScript 编译器检查测试文件例如npx tsc --noEmit questions/03062-medium-shift/test-cases.ts仓库根目录的 tsconfig.base.json 启用了strict模式保证约束与ts-expect-error断言都被严格校验若无任何编译错误说明 5 个用例全部通过。对于移除首元素但保留剩余元组类型这类需求该方法同样可用于实际业务类型设计例如从事件参数元组中剔除event参数、解析路由参数列表时丢弃首个 token 等场景。总结围绕 type-challenges 的 3062・Shift 题目本文完成了从题目解读、测试基准分析到多解法推导与边界论证的完整闭环核心解法type ShiftT extends any[] T extends [infer First, ...infer Rest] ? Rest : []用条件类型 rest 元素推断实现删除首元素约束是硬性要求extends any[]不仅保障类型安全更是通过ts-expect-error Shiftunknown用例的前提空元组与单元素元组都经由不同路径得到[]行为符合运行时直觉横向对比Pop/Push/Unshift后可以提炼规律删除类操作依赖infer推断剩余部分插入类操作只需 rest 展开构造。掌握了Shift你就掌握了元组模式匹配与 rest 推断这对类型体操的基础招式后续无论是 Flatten 这类递归展平还是 Reverse 这类元组反转其核心都是同一套infer rest 的递归拆解技巧。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐type-challenges 中阶挑战题解在类型系统里实现 Array.lastIndexOf——LastIndexOf\T, U\ 完整推导type challenges 中阶挑战题解在类型系统里实现 Array.lastIndexOf——LastIndexOf\T, U\ 完整推导 本篇围绕示例工程type-challenges 中阶挑战用类型系统实现联合类型的全排列 Permutationtype challenges 中阶挑战用类型系统实现联合类型的全排列 Permutation 本篇文章以 type challenges 仓库的 00296示例工程Clypra项目部署指南从源码到可执行文件的完整发布流程Clypra项目部署指南从源码到可执行文件的完整发布流程 Clypra是一款基于Tauri、React和TypeScript构建的现代视频编辑器致力于提供免音视频视频视频处理桌面应用上一篇pwru输出格式解析从JSON到元数据的完整解读下一篇G0DM0D3三层遥测架构设计ZDR元数据、客户端信标与opt-in数据集创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网