示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载导读本题是 type-challenges 题库中编号 3062 的中级Medium数组类题目要求用 TypeScript 类型系统实现Array.shift的类型版本输入一个元组输出去掉首元素后的新元组。读完本文你将掌握 variadic tuple types可变长元组类型与infer条件类型的组合用法理解「约束即过滤」的边界处理思想并学会从测试用例反推类型设计的完整路径。题目速览与挑战目标题目原文位于 questions/03062-medium-shift/README.md日文版见 questions/03062-medium-shift/README.ja.md由 jiangshanmetadifficulty: medium title: Shift tags: array related: 16题面非常精炼只有一句话和一段示例Implement the type version ofArray.shifttype Result Shift[3, 2, 1] // [2, 1]运行时 JavaScript 的Array.prototype.shift()会删除数组的第一个元素并返回它而这道题要求的方向恰好相反——ShiftT要返回去掉首元素之后剩下的数组。这与Pop去掉尾元素正好是一对镜像操作也是官方在 Pop 题目 末尾给出的扩展练习之一Extra: Similarly, can you implementShift,PushandUnshiftas well?测试用例答案的唯一裁判题目没有附带任何题解提示判定答案对错的唯一依据是测试文件 questions/03062-medium-shift/test-cases.ts。先完整读一遍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], ]逐条拆解可以反推出设计者的全部要求测试用例期望结果蕴含的约束Shiftunknown必须产生类型错误ts-expect-error泛型参数不能是任意类型需要约束为数组Shift[][]空元组应当原样返回空元组Shift[1][]单元素元组去掉首元素后为空Shift[3, 2, 1][2, 1]普通元组去掉首元素且保留元组类型而非退化为数组Shift[a, b, c, d][b, c, d]元素类型任意数字、字符串均可与具体类型无关Equal与Expect来自工作区工具包 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 : falseEqual通过「函数签名逆变比较」这一经典手法实现严格的类型全等比较它要求Shift[3, 2, 1]与[2, 1]在结构上完全一致——这解释了下表中「保留元组类型」为什么是硬性要求如果实现返回的是number[]这样的宽松数组类型Equal会判定为false导致用例失败。参考实现一行完成初始模板位于 questions/03062-medium-shift/template.ts只有一个占位符type ShiftT any标准解法借助 TypeScript 4.0 引入的 variadic tuple types一行即可通过全部用例type ShiftT extends any[] T extends [infer First, ...infer Rest] ? Rest : []把模板改成上面这行就是这道题的完整答案。下面逐步剖析每一部分的职责约束T extends any[]从测试第一行ts-expect-error Shiftunknown可知Shift只对数组/元组类型有意义。加上约束后Shiftunknown会在传入参数时就产生「类型 unknown 不满足约束 any[]」的编译错误恰好命中ts-expect-error的预期。条件类型T extends [infer First, ...infer Rest] ? ... : ...使用元组模式匹配。infer First捕获首元素...infer Rest捕获剩余部分。若元组至少有一个元素则匹配成功返回Rest。else 分支[]空元组[]无法匹配「至少一个元素」的模式落入此分支返回空元组[]对应Shift[]与Shift[1]两个用例。为什么 infer 能保留元组形态...infer Rest的位置很关键。在元组模式中位于...之后的infer会被推断为元组类型而非普通数组因此Shift[3, 2, 1]得到的是[2, 1]而不是number[]。这一点正是EqualShift[3, 2, 1], [2, 1]能够成立的前提。可以理解为infer的推断结果会尽量保持被匹配位置的形状shape。变体与取舍除了标准答案还有几种思路各有取舍不妨对照// 变体 1不关心首元素的具体值 type ShiftT extends any[] T extends [unknown, ...infer Rest] ? Rest : [] // 变体 2与 First/Pop 同款风格先取 First 再拼接 type ShiftT extends any[] T extends [infer First, ...infer Rest] ? Rest extends [...infer Tail] ? Tail : never : [] // 变体 3去掉约束但会破坏 ts-expect-error 用例 type ShiftT T extends [infer First, ...infer Rest] ? Rest : []变体 1用unknown占位首元素语义等价且更强调「我们不在乎被丢弃的元素是什么」。变体 3去掉extends any[]约束后Shiftunknown会落入 else 分支返回[]而非报错ts-expect-error用例即告失败——这从反面证明了「约束即过滤」把非法输入挡在类型层面而不是让它们静默流进实现。从源码结构看First、Last等同类数组题如 00014-easy-first、00015-medium-last都复用同一套「模式匹配 infer」范式本题是这一范式的又一次演练。同族挑战一条完整的数组类型操作链info.yml中的related: 16指向 Pop 题目。而 Push3057Easy 与 Unshift3060Easy 与之构成完整的四件套题目编号难度操作方向Push3057easyPush[1, 2], 3→[1, 2, 3]尾部追加Unshift3060easyUnshift[1, 2], 0→[0, 1, 2]头部追加Pop16mediumPop[a,b,c,d]→[a,b,c]尾部删除Shift3062mediumShift[3, 2, 1]→[2, 1]头部删除对照实现可以更清晰地感受对称性——Pop把...infer Rest放在前面匹配Shift放在后面匹配type PopT extends any[] T extends [...infer Rest, infer Last] ? Rest : [] type ShiftT extends any[] T extends [infer First, ...infer Rest] ? Rest : []掌握了这两个方向再回头做 Push/Unshift 时会发现它们不过是把「删除」换成「追加」底层依赖的是同一条语法。本地验证与提交方式仓库采用「模板 测试用例」的练习结构template.ts留空、test-cases.ts断言验证答案时只需把实现写入 template.ts然后使用 TypeScript 编译器对测试文件做类型检查即可# 在仓库根目录安装依赖后对单个题目做类型检查 pnpm install npx tsc --noEmit questions/03062-medium-shift/test-cases.ts若输出中不存在任何类型错误说明实现通过全部Expect断言注意ts-expect-error行是「预期报错」它本身不会被计入失败若某个Expect处报出「类型不满足约束 true」之类的错误则说明对应断言未通过。仓库根目录 package.json 中同时提供了lint、translate等工程化脚本说明该项目的题目元数据info.yml、多语言 READMEREADME.ja.md等均由脚本驱动维护题目本身则保持纯类型练习的轻量形态。小结本题用可变长元组模式匹配[infer First, ...infer Rest]在类型层面实现Array.shift的语义核心返回值必须保留元组形态否则无法通过Equal严格相等断言。约束T extends any[]不是可有可无的装饰而是「约束即过滤」的实践它让Shiftunknown这类非法调用在类型检查阶段直接报错与测试用例第一行的ts-expect-error形成呼应。与 Pop/Push/Unshift 连成一线后你会发现 TypeScript 类型体操处理数组的四类基本操作其实共享同一套「模式匹配 infer」的心智模型在仓库中顺着 questions 目录逐个通关即可系统化掌握这套模型。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐type-challenges 中阶挑战解析用 TypeScript 类型系统实现 Array.shiftShift\T\type challenges 中阶挑战解析用 TypeScript 类型系统实现 Array.shiftShift\T\ 本篇技术指南以 type示例工程type-challenges 第 14 题 First of Array 实战用类型体操实现 FirstT 提取数组首元素类型type challenges 第 14 题 First of Array 实战用类型体操实现 FirstT 提取数组首元素类型 本篇文章以 type ch示例工程type-challenges 第 19749 题 IsEqual用类型体操实现类型相等判定type challenges 第 19749 题 IsEqual用类型体操实现类型相等判定 导读 本文聚焦 type challenges https://示例工程上一篇Gofile极速下载器3倍加速、断点续传的终极文件下载方案下一篇DS4Windows终极指南3步让PS4手柄在PC上完美工作创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考