- 示例工程
【免费下载链接】type-challenges
Collection of TypeScript type challenges with online judge
本文聚焦 type-challenges 仓库中的 Easy 级题目Parameters(questions/03312-easy-parameters)。该题要求你在不使用 TypeScript 内置Parameters<T>的前提下,用条件类型与infer关键字手写出等价的MyParameters<T>,用于提取任意函数的参数类型元组。读完本文,你将彻底掌握infer在函数类型匹配中的用法、元组(tuple)类型的构建方式,以及内置Parameters<T>的底层实现原理,并能将同样的推断技巧迁移到ReturnType、ConstructorParameters等更多场景。
题目速览:实现内置 Parameters<T>
题目原文(韩文版 README.ko.md,与英文版 README.md 一致)要求如下:
내장 제네릭
Parameters<T>를 이를 사용하지 않고 구현하세요.(不使用内置泛型Parameters<T>,自行实现它。)
题目给出的示例:
const foo = (arg1: string, arg2: number): void => {} type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]也就是说,MyParameters<typeof foo>应当得到一个元组类型[string, number],它恰好对应foo的两个参数的类型。注意 TypeScript 元组可以携带具名标签(如[arg1: string, arg2: number]),题目注释里展示的正是带标签的形式,但等价的普通元组[string, number]同样满足要求。
这道题在仓库中被标注为easy难度,并带有#infer、#tuple、#built-in三个标签:infer是本题的核心解题工具,tuple是输出结果的类型形态,built-in表示目标是对内置工具类型的复刻。
从模板文件出发:理解类型约束
每个挑战在仓库中都配有模板文件,本题的模板是 template.ts:
type MyParameters<T extends (...args: any[]) => any> = any模板已经给出了类型参数T的约束条件:T extends (...args: any[]) => any。这意味着:
T必须是一个函数类型;- 该函数可以接收任意个、任意类型的参数(
...args: any[]); - 返回值类型不限(
=> any)。
这个约束保证了后续对T做模式匹配时不会遇到非函数类型。模板中的= any是待填空的占位符,需要替换为真正的实现。在 JavaScript 运行时层面上,typeof foo拿到的是函数本身;而在类型层面上,typeof foo得到的是它的函数类型(arg1: string, arg2: number) => void——这正是我们要做类型提取的对象。
核心解法:条件类型 + infer
TypeScript 的条件类型(conditional type)允许我们以T extends X ? Y : Z的形式对类型做模式匹配。当X中出现infer关键字时,TypeScript 会尝试推断出对应位置的类型,并在真分支中将其作为新类型变量使用。
据此,标准解法如下:
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never逐段拆解:
T extends (...args: infer P) => any:把传入的函数类型T与"具有任意参数列表的函数"这一模式做匹配,infer P让 TypeScript 推断出整个参数列表的类型;- 由于模板约束保证了
T一定是函数类型,匹配必然成功,直接走真分支返回P; P被推断为参数元组,例如对(arg1: string, arg2: number) => void会得到[string, number];- 假分支返回
never仅是防御性写法,实际因约束的存在不会触发。
这正是 TypeScript 内置Parameters<T>的官方实现:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;由此可见,本题的"标准答案"与 TypeScript 内置工具类型的源码几乎一字不差——infer P推断出的P天然就是一个元组类型,不需要任何额外的拼接或转换。这就是该题目同时打上#infer与#tuple两个标签的原因:答案本身就是一个 infer 推断出的元组。
用仓库测试用例验证:Equal 与 Expect
仓库为每个题目都提供了在线判题的测试文件。本题的 test-cases.ts 定义了三个函数与三组断言:
import type { Equal, Expect } from '@type-challenges/utils' function foo(arg1: string, arg2: number): void {} function bar(arg1: boolean, arg2: { a: 'A' }): void {} function baz(): void {} type cases = [ Expect<Equal<MyParameters<typeof foo>, [string, number]>>, Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>, Expect<Equal<MyParameters<typeof baz>, []>>, ]测试覆盖了三种典型场景:
| 被测函数 | 期望结果 | 覆盖要点 |
|---|---|---|
foo(arg1: string, arg2: number): void | [string, number] | 多参数、基础类型 |
bar(arg1: boolean, arg2: { a: 'A' }): void | [boolean, { a: 'A' }] | 含对象字面量类型的参数 |
baz(): void | [] | 零参数函数应得到空元组 |
其中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 : falseExpect<T>要求T必须是字面量true,否则编译报错;Equal<X, Y>通过函数类型协变比较实现了对两个类型完全相等的严格判定(比extends的双向可赋值更严格,能区分any、never等微妙情形)。测试用例说明:
MyParameters<typeof foo>必须精确等于[string, number],而不是any、unknown[]或超集类型;- 零参数函数
baz的参数列表推断为空元组[],证明infer P对空参数同样成立。
在本地验证时,可将 template.ts 中的占位符替换为T extends (...args: infer P) => any ? P : never,再配合 test-cases.ts 运行 TypeScript 编译检查(仓库根目录的 package.json 声明了typescript依赖),若全部断言通过则解答正确。
边界情况与注意事项
- 零参数函数:
baz(): void会被推断为[],这符合Parameters的行为——返回空元组而非never; - 带默认值或可选参数:
(a?: string, b = 1) => void会推断为[a?: string, b?: number](可选参数对应可选元组元素),推断逻辑由 TypeScript 的类型推断规则直接给出; - 带标签元组:若使用具名参数,推断结果可能保留参数名(如
[arg1: string, arg2: number]),这与题目注释展示的形式一致; - 泛型函数:对
function id<T>(x: T): T,Parameters<typeof id>推断为[x: unknown],因为typeof拿到的是泛型函数的具体化类型; - 约束之外的类型:模板约束
T extends (...args: any[]) => any使MyParameters<number>这类调用直接报类型错误,保证了解答的安全性。
延伸:infer 在工具类型中的复用
掌握本题后,infer的其他应用几乎是同一思路的变体,这也解释了为什么本题被归类为 easy。仓库中同属 infer 主题的进阶题目还有:
- 00002-medium-return-type:用
T extends (...args: any[]) => infer R ? R : any提取返回值类型; - 00003-medium-omit:用
keyof T与映射类型实现对象键的剔除; - 00062-medium-type-lookup:在联合类型上用
T extends { type: infer U }做按字段匹配的查找。
它们的共同模式可以概括为:先用条件类型约束目标形态,再用infer在指定位置提取类型,最后在真分支中组织输出。你还可以把infer用在数组元素提取(T extends [infer F, ...infer R])、字符串模板匹配(T extends \${infer A}-${infer B}`)等场景,这些技巧在仓库 [guides](https://link.gitcode.com/i/047f64a4c7dc4f3a1e583196f478bbbf) 目录的进阶指南中均有涉及。可以说,把Parameters ` 这道题吃透,就等于打开了 TypeScript 类型编程的大门。
小结
第 3312 题 Parameters 的核心收获有三点:其一,typeof运算符可以把运行时函数值转换为类型层面的函数类型;其二,条件类型配合infer能够从函数类型中精确提取参数列表,且结果天然是元组;其三,一行实现T extends (...args: infer P) => any ? P : never恰好就是内置Parameters<T>的官方源码,体现了"复刻内置工具类型"这类题目(#built-in)的典型解题路径。对照仓库中的 template.ts 与 test-cases.ts 完成填空并通过测试,你就完成了本次挑战。
- 示例工程
【免费下载链接】type-challenges
Collection of TypeScript type challenges with online judge
相关推荐
type-challenges 第 14 题:实现 First<T> 泛型,提取元组首元素类型
type challenges 第 14 题:实现 First<T> 泛型,提取元组首元素类型 本篇指南基于 type challenges 仓库中
示例工程type-challenges 题目精解:不使用内置泛型实现 Omit<T, K>(Medium)
type challenges 题目精解:不使用内置泛型实现 Omit<T, K (Medium) 导读 本篇文章围绕 type challenges http
示例工程TypeScript 类型挑战:手写实现内置 `Readonly<T>` 泛型(type-challenges 第 7 题)
TypeScript 类型挑战:手写实现内置 Readonly<T 泛型(type challenges 第 7 题) 本篇指南聚焦 type challeng
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考