ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TypeScript 类型挑战 2:不使用内置 ReturnType 手写函数返回类型提取(infer 实战指南)

TypeScript 类型挑战 2:不使用内置 ReturnType 手写函数返回类型提取(infer 实战指南) 示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载本文围绕 type-challenges 仓库中的00002-medium-return-typeGet Return Type挑战展开完整还原题目要求不借助 TypeScript 内置ReturnTypeT工具类型独立实现一个能从函数类型中提取返回类型的MyReturnTypeT。通过拆解条件类型与infer关键字的配合原理并结合本仓库的template.ts起始模板与test-cases.ts测试用例逐条验证读者将掌握类型级模式匹配的核心心法并顺带打通ParametersT、AwaitedT等同类工具类型的实现思路。挑战概览题目是什么该挑战位于 questions/00002-medium-return-type是 type-challenges 中编号#2的中等难度medium题目。根据其 info.yml 元数据它的标签为infer与built-in由作者 Anthony Fu 提供。题目原文要求只有一句话Implement the built-inReturnTypeTgeneric without using it.不使用ReturnType实现 TypeScript 的ReturnTypeT泛型。即我们需要自己写一个MyReturnTypeT使其行为与内置的ReturnTypeT一致——输入一个函数类型输出该函数声明或推断的返回值类型。题目给出的核心示例const fn (v: boolean) { if (v) return 1 else return 2 } type a MyReturnTypetypeof fn // 应推导出 1 | 2注意这里的fn没有显式标注返回类型1和2会被 TypeScript 收窄为字面量类型因此typeof fn的返回类型是1 | 2联合类型MyReturnTypetypeof fn必须原样输出这个联合类型。起步模板 template.ts 中只有一个占位实现type MyReturnTypeT any我们的任务就是把any替换成真正的类型逻辑并通过全部测试用例。先理解目标内置 ReturnType 到底做了什么ReturnTypeT是 TypeScript 标准库中预置的实用工具类型utility type其作用是从一个函数类型中取出返回类型。例如type R1 ReturnType() string // string type R2 ReturnType() 123 // 123 type R3 ReturnType() () foo // () foo它在日常开发中常用于从已有函数推断结果类型例如把某个 API 函数的返回值类型提取出来复用。本题正是要求我们用类型编程的方式复刻它从而理解它背后的实现机制——也就是infer关键字。解法拆解条件类型 infer 提取返回类型第一步约束泛型必须是一个函数内置ReturnTypeT的泛型约束是T extends (...args: any) any即只接受函数类型。我们手写时也可以先加上同样的约束让不合法输入在编译期就报错type MyReturnTypeT extends (...args: any) any any其中(...args: any) any是任意函数的类型写法参数可以是任意数量和任意类型用 rest 参数 any表示返回任意类型。用(...args: any[]) any也是常见写法两者在本题场景下等价。第二步用条件类型做模式匹配TypeScript 的条件类型语法形如T extends U ? X : Y它的强大之处在于当T与U匹配时可以在U中用infer声明一个待推断的类型变量由编译器根据T的实际结构自动填充。函数类型(...args: any) infer R中infer R表示返回值类型暂时未知请编译器从T中推断出来。于是核心解法如下type MyReturnTypeT extends (...args: any) any T extends (...args: any) infer R ? R : any逐段解释片段作用T extends (...args: any) any外层泛型约束只接受函数类型T extends (...args: any) infer R条件判断匹配任意函数结构同时把返回值类型绑定到R? R匹配成功返回推断出的返回类型: any兜底分支由于约束已保证是函数实际不会走到第三步验证题目示例回到题目的fnconst fn (v: boolean) v ? 1 : 2 type a MyReturnTypetypeof fn // 1 | 2typeof fn是(v: boolean) 1 | 2。把它代入条件类型后编译器在(...args: any) infer R中匹配成功并将R推断为1 | 2——与题目要求完全一致。这正是条件类型分发式推断的体现函数签名的返回位置有多少种字面量可能infer R就收拢成多大的联合类型。源码级验证对照测试用例逐条检查在 test-cases.ts 中本挑战共准备了 7 组测试断言import type { Equal, Expect } from type-challenges/utils type cases [ ExpectEqualstring, MyReturnType() string, ExpectEqual123, MyReturnType() 123, ExpectEqualComplexObject, MyReturnType() ComplexObject, ExpectEqualPromiseboolean, MyReturnType() Promiseboolean, ExpectEqual() foo, MyReturnType() () foo, ExpectEqual1 | 2, MyReturnTypetypeof fn, ExpectEqual1 | 2, MyReturnTypetypeof fn1, ] type ComplexObject { a: [12, foo] bar: hello prev(): number }逐条分析我们的解法如何通过() string→string最朴素的函数返回类型。() 123→123字面量类型被完整保留而不是被放大为number。() ComplexObject→ComplexObject返回复杂对象结构含数组、字符串字面量属性和方法prev(): number时infer R会原样提取整个对象类型。() Promiseboolean→Promiseboolean注意infer R只剥离一层函数返回类型不会递归解包 Promise 内部类型那是另一道题 00189-easy-awaited 的任务。() () foo→() foo返回类型本身是一个函数类型infer R能正确推断出返回函数的函数的返回类型。typeof fn→1 | 2无显式返回注解、基于条件分支的返回被推断为字面量联合。typeof fn1→1 | 2fn1形如(v: boolean, w: any) v ? 1 : 2带有两个参数证明infer R的提取只关注返回值位置与参数数量、参数类型无关。这里用到的断言工具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 : falseExpectT extends true要求传入的必须是true类型否则该行声明会编译报错EqualX, Y则用函数返回类型协变比较的技巧做严格相等判断能够区分1与number这类可互相赋值但不等价的结构。因此只要MyReturnType的结果与期望类型哪怕有一丁点差别比如把123放大成了number整个测试文件都会编译失败——这就是本挑战在线判题的底层机制。边界与陷阱为什么不能写成其他形式不能直接用T的索引访问有人会想函数类型是对象结构能不能用T[return]之类的方式取返回类型TypeScript 的函数类型并不暴露可索引的属性键这种写法无法成立函数签名的拆解正是infer的用武之地。兜底分支别用never条件类型写成: any与: never都能通过本题测试因为泛型约束保证T必为函数真分支恒成立。但内置ReturnType的兜底是any保持一致更贴近复刻内置工具类型的题目语义写成never在约束被放宽时会产生不同的行为属于细节差异了解即可。参数位置用any而非具名参数(...args: any) any中参数用 rest any是最宽松的匹配方式确保任意签名的函数都能匹配上。如果写成(arg: number) infer R那么返回类型相同但参数不同的函数就会落入兜底分支导致类型错误。举一反三同类 infer 模式的关联挑战本题的infer技巧在 type-challenges 中反复出现掌握它可以顺势解决一系列题目03312-easy-parameters把infer R挪到参数位置(...args: infer P) any即可提取函数参数元组这正是内置ParametersT的实现。00189-easy-awaitedinfer R配合递归AwaitedR实现Promise类型的逐层解包。00010-medium-tuple-to-union在数组/元组类型上使用T[number]等手段将元组展开为联合。它们共通的思维方式是把类型结构是否匹配表达为条件类型用infer在匹配处声明变量让编译器替你完成推断。本题是这条思维链上最基础、最干净的一环。本地运行与验证type-challenges 支持在本地 IDE 中做题与校验。根据仓库根目录 README.md 的 Play Locally 说明前提是安装最新版 Node.js 与 pnpm然后pnpm install随后运行生成脚本对应根 package.json 中的pnpm generatepnpm generate生成的练习文件即可在支持 TypeScript 的编辑器里编写MyReturnType并通过编译器对test-cases.ts的静态检查来验证答案。需要留意的是全部挑战均在strict 模式下运行见根 README.md 的说明以及 tsconfig.base.json 中开启的strict: true因此解题过程中涉及的可空性、类型收窄等行为都会按严格模式判定。如果想直接在本仓库查看本题素材可以从 template.ts起始模板出发用上面的解法替换any再对照 test-cases.ts 确认全部断言通过中文题目说明可参考 README.zh-CN.md。小结MyReturnTypeT的完整实现只有三行type MyReturnTypeT extends (...args: any) any T extends (...args: any) infer R ? R : any它背后承载了三个必须内化的知识点泛型约束限定输入范围、条件类型提供结构匹配的分支语义、infer声明由编译器推断的占位类型变量。三者叠加就让 TypeScript 的类型系统具备了从复杂类型中拆出任意组成部分的能力。以此题为起点再去挑战Parameters、Awaited乃至更复杂的类型递归都将水到渠成。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐TypeScript 类型挑战 00002手写 ReturnTypeT用 infer 提取函数返回类型TypeScript 类型挑战 00002手写 ReturnTypeT 用 infer 提取函数返回类型 本篇文章围绕 type challenges 仓示例工程gh-aw防注入完全指南模板注入与Shell注入防御实战gh aw防注入完全指南模板注入与Shell注入防御实战 ️ gh aw GitHub Agentic Workflows是 GitHub 推出的智能示例工程TypeScript 函数返回类型推断Type from Func Return实战指南从隐式推断到 ReturnType 提取TypeScript 函数返回类型推断Type from Func Return实战指南从隐式推断到 ReturnType 提取 导读 本文基于 The文档教程上一篇claude-seo 语义主题聚类实战用 SERP 重叠数据驱动 Hub-and-Spoke 内容架构规划下一篇docopt.rs高级技巧自定义类型解码与错误处理最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表