- 示例工程
【免费下载链接】type-challenges
Collection of TypeScript type challenges with online judge
type-challenges 是一个带在线判题功能的 TypeScript 类型挑战集合,第 15 题「最后一个元素(Last of Array)」要求实现一个名为Last<T>的类型泛型:它接收一个数组类型T,并返回该数组最后一个元素的类型。本文将以 第 15 题中文 README 为骨架,结合仓库中的 模板文件、测试用例 与相关挑战,讲解从题目理解、边界情况分析到多种解法原理的完整链路,读完你将掌握infer+ 可变元组类型(variadic tuple types)提取数组尾部元素的方法,并能在本地跑通测试验证。
题目速览:从数组类型中推导最后一个元素
原题描述非常简洁:实现一个Last<T>泛型,它接受一个数组T并返回其最后一个元素的类型,题目难度为medium,标签为array,由 Anthony Fu 出题,并建议在 TypeScript 4.0 下完成(info.yml 中记录了difficulty: medium、tags: array、related: 14, 16)。
题目给出的示例:
type arr1 = ['a', 'b', 'c'] type arr2 = [3, 2, 1] type tail1 = Last<arr1> // 应推导出 'c' type tail2 = Last<arr2> // 应推导出 1你需要填补 template.ts 中唯一的占位:
type Last<T extends any[]> = any把any换成你的实现,使其通过全部测试用例。
测试用例:答案的唯一裁判
在 test-cases.ts 中可以看到本题的四个验收标准:
import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Last<[]>, never>>, Expect<Equal<Last<[2]>, 2>>, Expect<Equal<Last<[3, 2, 1]>, 1>>, Expect<Equal<Last<[() => 123, { a: string }]>, { a: string }>, ]这些用例透露了三个关键信息:
- 空数组返回
never:Last<[]>必须是never。这要求你的实现必须处理空元组的分支,不能抛错也不能返回any。 - 单元素元组返回自身:
Last<[2]>推导为2,说明推导结果必须是字面量类型而不是number之类的宽化类型——这正是元组(tuple)与普通数组类型的关键区别。 - 元素类型可以是任意复杂类型:最后一个用例中的元素是
() => 123函数类型和{ a: string }对象类型,Last必须原样返回{ a: string },证明它不关心元素是什么,只关心位置。
其中Equal与Expect来自仓库的 utils/index.d.ts:
export type Expect<T extends true> = T export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : falseEqual是严格相等检查(基于函数类型参数逆变性的判断),比简单的extends互推更严格,因此你的实现必须精确匹配目标类型,而不是“近似成立”。
标准解法:条件类型 + infer + 可变元组
最经典、也是 TypeScript 官方文档推荐思路的解法如下:
type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never逐段拆解:
T extends any[]:约束Last的入参必须是数组或元组类型,保证T['length']等数组内建能力可用;[...infer _, infer L]:这是可变元组类型(variadic tuple type,TypeScript 4.0 引入)与infer的配合。模式[...前缀, 尾元素]表示“把元组分成前 N-1 项和最后一项两部分”,infer _捕获前面所有元素(用_命名表示我们并不关心),infer L捕获最后一个元素的类型;? L : never:如果T能匹配上述模式(即至少有一个元素),返回最后一项类型;空元组[]无法拆出“最后一项”,走never分支,正好满足Last<[]>的验收要求。
以Last<['a', 'b', 'c']>为例,类型系统把['a', 'b', 'c']与[...infer _, infer L]对齐:_被推导为['a', 'b'],L被推导为'c',最终返回'c'。
为什么题目建议使用 TypeScript 4.0
[...infer _, infer L]这种在元组尾部进行“变长前缀 + 单元素”的模式匹配,依赖可变元组类型(variadic tuple types),这是 TypeScript 4.0(2020 年 8 月发布)的核心新特性。在 4.0 之前,infer只能捕获固定长度的元组片段,无法表达“任意长度的前缀”,因此这道题才会在描述中给出“在此挑战中建议使用 TypeScript 4.0”的提示。仓库根目录 package.json 的 devDependencies 使用typescript: ^5.3.3,在本地用现代版本验证本题没有任何兼容问题。
另一种思路:借助索引访问与T['length']
有经验的读者可能还会想到用元组索引来取尾元素。由于元组类型保留了精确的长度信息,[3, 2, 1]的T['length']就是字面量3,于是可以构造:
type Last<T extends any[]> = [any, ...T][T['length']]思路是:在T前面“垫”一个any,使新元组的长度比T大 1,那么下标T['length']恰好指向原元组最后一个位置。例如[3, 2, 1]变成长度为 4 的[any, 3, 2, 1],取下标 3 得到1。
但要注意它的边界缺陷:当T是[]时,T['length']为 0,[any][0]推导为any,无法满足测试用例要求的never,除非额外叠加T extends [] ? never这样的分支。相比之下,[...infer _, infer L] ? L : never天然覆盖空数组分支,更加干净、推荐优先掌握。
本地验证:如何跑通测试
这是一个 pnpm workspace 项目,@type-challenges/utils通过workspace:*协议链接(见根目录 package.json)。你可以在项目根目录执行:
pnpm install安装完成后,把 template.ts 中any替换为你的实现,然后让 TypeScript 编译器按 tsconfig.json 检查 test-cases.ts:
pnpm exec tsc --noEmit questions/00015-medium-last/test-cases.ts或者直接让 IDE 的 TS Server 对测试文件做类型检查。如果实现正确,Expect<Equal<...>>全部通过;如果实现有误(例如返回了unknown或没有处理空数组),Equal会得到false,Expect<T extends true>立即报类型错误,这正是本项目“用类型系统当裁判”的判题方式。
在挑战序列中的定位:与 First、Pop 串联学习
题目是数组系列挑战链上的一环,info.yml 明确标注related: 14, 16:
- 第 14 题「第一个元素」(easy,README.zh-CN.md):实现
First<T>返回数组首元素,标准写法是T extends [infer F, ...any[]] ? F : never。它与本题互为镜像——一个拆头部、一个拆尾部; - 第 16 题「排除最后一项」(medium,README.zh-CN.md):实现
Pop<T>返回去掉最后一项后的数组,标准写法T extends [...infer R, any] ? R : never,其 test-cases.ts 还要求Pop<[]>得到[]。
把三道题放一起对比非常直观:First用[infer F, ...]从头部切,Last用[...infer _, infer L]从尾部切,Pop用[...infer R, any]把尾部“吞掉再吐前缀”。三者共享同一套可变元组匹配语法,只要吃透其中一道,另外两道都能顺手解决——这也是 type-challenges 设计挑战序列的用意所在。
小结
Last<T>这道 medium 题的核心价值在于让你熟练掌握两个 TypeScript 高级类型能力:
- 条件类型 +
infer的元组模式匹配:[...infer _, infer L]是“切分元组”的通用范式,后续的Pop、Shift、Slice等大量数组类挑战都建立在这一范式之上; - 边界意识:空数组要返回
never,这是测试用例强制的行为,也提醒我们在类型设计中永远要考虑空输入。
动手实践建议:先自己写一版提交到 在线判题(题目 README 中的接受挑战入口),再对照本文标准解法检查边界,最后顺手把第 14、16 题一起刷掉,形成“首元素 / 尾元素 / 去掉尾元素”的完整知识闭环。
- 示例工程
【免费下载链接】type-challenges
Collection of TypeScript type challenges with online judge
相关推荐
CUDA 加速 Black-Scholes 期权定价:cuda-samples BlackScholes 示例源码级解析
CUDA 加速 Black Scholes 期权定价:cuda samples BlackScholes 示例源码级解析 导读 本文基于 NVIDIA CUDA
示例工程TypeScript 类型挑战 15:实现 `Last<T>` 泛型,提取数组最后一个元素类型
TypeScript 类型挑战 15:实现 Last<T 泛型,提取数组最后一个元素类型 导读 Last<T 是 type challenges 题库中的第 1
示例工程Boss Show Time:招聘时间可视化终极指南,让你的求职更高效
Boss Show Time:招聘时间可视化终极指南,让你的求职更高效 还在为招聘平台上的模糊时间信息而烦恼吗?Boss Show Time是一款专为求职者设计
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考