TypeScript infer 实战:手把手教你从数组里‘抠’出想要的类型(附4个常用工具类型源码)

张开发
2026/6/11 5:48:13 15 分钟阅读

分享文章

TypeScript infer 实战:手把手教你从数组里‘抠’出想要的类型(附4个常用工具类型源码)
TypeScript infer 实战从数组类型中精准提取元素的四种高阶技巧在TypeScript的类型系统中infer关键字就像一把精巧的手术刀能让我们在类型层面进行精确的解剖操作。对于已经掌握TS基础的开发者来说熟练运用infer意味着能将类型安全提升到全新维度——特别是在处理复杂数据结构时。本文将带你从实战角度通过四个典型场景掌握如何用infer从数组类型中提取所需元素。1. 理解infer的核心机制infer的本质是类型模式匹配中的变量声明。当我们在条件类型中使用extends进行匹配时infer可以捕获匹配到的部分作为临时类型变量。这个特性在处理嵌套类型时尤其强大就像在运行时用解构赋值提取值一样只不过这是在编译时对类型进行操作。考虑这个简单例子type UnpackPromiseT T extends Promiseinfer U ? U : never这里infer U会捕获Promise包裹的实际类型。如果传入PromisenumberU就是number。这种模式是infer最基础的用法但当我们把它应用到数组上时就能实现更精细的类型操作。2. 提取数组首元素类型处理API响应时经常需要获取数组第一个元素的类型。假设我们有一个用户列表API返回User[]但某个页面只需要展示第一个用户的信息。这时就需要安全地提取首元素类型type HeadT extends any[] T extends [infer First, ...any[]] ? First : never // 实战应用 interface User { name: string; age: number } type UserList [User, ...User[]] type FirstUser HeadUserList // FirstUser ≡ User关键点解析T extends [infer First, ...any[]]匹配至少有一个元素的数组...any[]表示我们不关心剩余元素的类型如果匹配失败空数组返回never保证类型安全在Redux中这种技巧可以用来定义action payload的类型约束type ActionPayloadT extends any[] HeadT3. 获取数组末尾元素类型与提取首元素相对应获取末尾元素在处理栈结构或最近记录时很有用。比如从操作历史记录中获取最后一步的类型type TailT extends any[] T extends [...any[], infer Last] ? Last : never // 应用示例 type OperationLogs [create, update, delete] type LastOperation TailOperationLogs // delete实现要点...any[]放在前面捕获除最后一个外的所有元素这种模式与JavaScript的rest参数位置正好相反同样用never处理空数组情况在表单多步骤流程中可以用它来确保当前步骤类型的准确性type FormSteps [info, payment, confirm] type CurrentStep TailFormSteps // confirm4. 移除数组首元素Shift操作在类型系统中模拟数组的shift操作可以用于处理参数列表的剩余部分。比如实现一个柯里化函数的类型定义type ShiftT extends any[] T extends [any, ...infer Rest] ? Rest : [] // 柯里化函数类型应用 type CurryParamsParams extends any[], Return Params extends [] ? Return : (arg: HeadParams) CurryParamsShiftParams, Return // 生成 (a: number) (b: string) boolean type CurriedFn CurryParams[number, string], boolean技术细节[any, ...infer Rest]确保至少有一个元素返回Rest即剩余部分空数组返回空数组保持类型安全5. 移除数组末尾元素Pop操作与shift相对pop操作在处理类似路径解析的场景很有用。比如从文件路径中移除最后一级type PopT extends any[] T extends [...infer Rest, any] ? Rest : [] // 路径处理示例 type PathParts [usr, local, bin] type ParentPath PopPathParts // [usr, local]进阶应用结合模板字面量类型可以构建类型安全的路径操作type JoinPathT extends string[] T extends [] ? : /${HeadT}${JoinPathShiftT} type FullPath JoinPath[user, profile, edit] // /user/profile/edit6. 构建你的类型工具库将上述工具类型组织起来就形成了一个基础但强大的类型工具库type ArrayUtils { Head: T extends any[](arr: T) HeadT Tail: T extends any[](arr: T) TailT Shift: T extends any[](arr: T) ShiftT Pop: T extends any[](arr: T) PopT } // 实际使用时可配合类型断言 declare const arrayUtils: ArrayUtils const first arrayUtils.Head([1, 2, true]) // number扩展建议添加Slice、Concat等更复杂的操作实现递归类型处理嵌套数组结合keyof和infer处理对象数组在大型项目中这样的类型工具能显著提升代码的可维护性。比如在API层定义响应类型时type ApiResponseT { data: T meta: { page: number } } type ExtractDataT T extends ApiResponseinfer D ? D : never // 自动提取出User[] type UserData ExtractDataApiResponseUser[]掌握这些技巧后你会发现TypeScript的类型系统远比表面看起来强大。infer就像类型编程中的显微镜让你能看到并操作类型系统的深层结构。

更多文章