当前位置: 首页 > news >正文

TypeScript类型体操:手把手教你用infer实现一个简易的‘类型提取’工具库

TypeScript类型体操:手把手教你用infer实现一个简易的‘类型提取’工具库

当你第一次看到infer关键字时,可能会觉得它像TypeScript类型系统中的某种黑魔法。但别担心,它实际上是一种强大的类型推导工具,能让我们在类型层面实现类似模式匹配的操作。今天,我们就来一起动手,用infer构建一个实用的类型工具库,就像type-festutility-types那样。

1. 理解infer的基础概念

infer是TypeScript中用于条件类型推导的关键字,它允许我们在类型系统中"提取"或"捕获"其他类型。想象一下,你有一个包裹在Promise中的类型,而你想知道这个Promise最终会返回什么——这就是infer的用武之地。

type UnpackPromise<T> = T extends Promise<infer U> ? U : never;

这个简单的类型工具可以提取Promise的泛型参数。让我们看个实际例子:

type User = { name: string; age: number }; type UserPromise = Promise<User>; type UnpackedUser = UnpackPromise<UserPromise>; // { name: string; age: number }

关键点

  • infer只能在extends子句的条件类型中使用
  • 它创建了一个临时的类型变量(上例中的U
  • 当匹配成功时,这个临时变量会被赋值为匹配到的类型

2. 构建基础类型提取工具

现在,让我们扩展这个基础概念,构建一些更实用的类型工具。

2.1 提取函数返回类型

type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never; function getUser(): User { return { name: "Alice", age: 30 }; } type UserReturnType = GetReturnType<typeof getUser>; // User

2.2 提取数组元素类型

type ArrayElement<T> = T extends (infer U)[] ? U : never; type Numbers = number[]; type NumberElement = ArrayElement<Numbers>; // number

2.3 处理嵌套Promise

有时候我们会遇到多层嵌套的Promise,这时候可以结合递归来解决:

type DeepUnpackPromise<T> = T extends Promise<infer U> ? DeepUnpackPromise<U> : T; type DeepPromise = Promise<Promise<Promise<User>>>; type DeepUnpacked = DeepUnpackPromise<DeepPromise>; // User

3. 高级infer技巧

3.1 协变与逆变

infer在不同位置会有不同的行为,这被称为协变和逆变:

// 协变示例 - 返回联合类型 type ExtractProperties<T> = T extends { name: infer U, age: infer U } ? U : never; const user = { name: "Bob", age: 25 }; type UserProps = ExtractProperties<typeof user>; // string | number // 逆变示例 - 返回交叉类型 type ExtractParams<T> = T extends { a: (arg: infer U) => void, b: (arg: infer U) => void } ? U : never; type Params = ExtractParams<{ a: (arg: number) => void, b: (arg: string) => void }>; // never (因为number & string是never)

3.2 元组操作

我们可以用infer来操作元组类型:

type FirstElement<T extends any[]> = T extends [infer First, ...any[]] ? First : never; type RestElements<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never; type Tuple = [string, number, boolean]; type First = FirstElement<Tuple>; // string type Rest = RestElements<Tuple>; // [number, boolean]

4. 构建完整类型工具库

现在,让我们把这些工具组合起来,创建一个更完整的类型工具库:

type TypeUtils = { // Promise相关 PromiseType: <T>(p: T) => T extends Promise<infer U> ? U : never; DeepPromiseType: <T>(p: T) => T extends Promise<infer U> ? DeepPromiseType<U> : T; // 函数相关 Parameters: <T extends (...args: any) => any>(fn: T) => Parameters<T>; ReturnType: <T extends (...args: any) => any>(fn: T) => ReturnType<T>; // 数组相关 ArrayElement: <T>(arr: T) => T extends (infer U)[] ? U : never; // 对象相关 PropertyType: <T, K extends keyof T>(obj: T, key: K) => T[K]; // 元组操作 First: <T extends any[]>(tuple: T) => FirstElement<T>; Rest: <T extends any[]>(tuple: T) => RestElements<T>; }; // 使用示例 declare const utils: TypeUtils; const userPromise = Promise.resolve({ name: "Charlie", age: 40 }); type UnpackedUser = typeof utils.PromiseType<typeof userPromise>; // { name: string; age: number }

5. 实战应用场景

5.1 API响应处理

在处理API响应时,我们经常需要处理Promise包装的数据:

async function fetchUser(): Promise<{ data: User }> { // API调用 } type ApiResponse = typeof utils.ReturnType<typeof fetchUser>; // Promise<{ data: User }> type UserData = typeof utils.PromiseType<ApiResponse>['data']; // User

5.2 高阶组件类型

在React中,我们可以用这些工具来增强高阶组件的类型安全:

function withUser<P>(Component: React.ComponentType<P & { user: User }>) { return function WrappedComponent(props: P) { const [user] = React.useState<User>({ name: "Dave", age: 50 }); return <Component {...props} user={user} />; }; } type OriginalProps = { id: string }; const EnhancedComponent = withUser<OriginalProps>(function Component({ id, user }) { return <div>{id}: {user.name}</div>; }); type WrappedProps = typeof utils.Parameters<typeof EnhancedComponent>[0]; // OriginalProps

5.3 状态管理类型推导

在Redux或类似状态管理中,我们可以提取action类型:

type ActionMap = { login: { type: "LOGIN"; payload: { token: string } }; logout: { type: "LOGOUT" }; }; type ActionTypes = { [K in keyof ActionMap]: ActionMap[K] }[keyof ActionMap]; // 等同于: type ActionTypes = | { type: "LOGIN"; payload: { token: string } } | { type: "LOGOUT" };

6. 性能考量与最佳实践

虽然infer很强大,但过度使用可能会影响类型检查性能:

  • 避免深层递归:TypeScript对递归深度有限制(默认约50层)
  • 使用类型别名缓存中间结果:这可以帮助编译器优化类型检查
  • 优先使用内置工具类型:如ParametersReturnType等,它们已经过优化
// 不推荐 - 每次使用时都会重新计算 type LongChain<T> = /* 复杂的类型运算 */; // 推荐 - 预先计算并缓存 type Step1 = /* 部分计算 */; type Step2 = /* 基于Step1的进一步计算 */; type FinalResult = /* 基于Step2的最终结果 */;

7. 扩展工具库

让我们再添加一些实用的类型工具:

type FlattenArray<T> = T extends (infer U)[] ? FlattenArray<U> : T; type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; }; type OptionalProps<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; type RequireProps<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

这些工具在处理复杂对象类型时特别有用:

type NestedArray = number[][][]; type FlatNumber = FlattenArray<NestedArray>; // number type ComplexObject = { a: string; b: { c: number; d: boolean[]; }; }; type ReadonlyComplex = DeepReadonly<ComplexObject>;

8. 测试你的类型工具

为了确保我们的类型工具工作正常,我们可以使用一些类型断言技巧:

type AssertEqual<T, U> = [T] extends [U] ? [U] extends [T] ? true : false : false; // 测试用例 type Test1 = AssertEqual< typeof utils.PromiseType<Promise<string>>, string >; // true type Test2 = AssertEqual< typeof utils.ArrayElement<string[]>, string >; // true type Test3 = AssertEqual< typeof utils.First<[string, number, boolean]>, string >; // true

9. 处理边缘情况

好的类型工具应该能够优雅地处理各种边缘情况:

// 处理never和any type SafeUnpack<T> = [T] extends [Promise<infer U>] ? U : T; type TestNever = SafeUnpack<never>; // never type TestAny = SafeUnpack<any>; // any // 处理联合类型 type UnpackUnion<T> = T extends Promise<infer U> ? U : T; type UnionTest = UnpackUnion<Promise<string> | number>; // string | number

10. 与TypeScript新特性结合

我们可以将infer与TypeScript的新特性如模板字面量类型结合:

type ExtractRouteParams<T> = T extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param | keyof ExtractRouteParams<`${Rest}`>]: string } : T extends `${string}:${infer Param}` ? { [K in Param]: string } : {}; type Params1 = ExtractRouteParams<"/user/:id">; // { id: string } type Params2 = ExtractRouteParams<"/post/:postId/comment/:commentId">; // { postId: string; commentId: string }

11. 类型安全的API设计

我们可以利用这些工具来创建类型安全的API客户端:

type ApiMethod<Req, Res> = (request: Req) => Promise<Res>; type ApiClient<Endpoints> = { [K in keyof Endpoints]: Endpoints[K] extends ApiMethod<infer Req, infer Res> ? (request: Req) => Promise<Res> : never; }; type MyApi = { getUser: ApiMethod<{ id: string }, User>; createUser: ApiMethod<Omit<User, 'id'>, { id: string }>; }; const client: ApiClient<MyApi> = { getUser: async ({ id }) => ({ name: "Eve", age: 60 }), createUser: async (user) => ({ ...user, id: "123" }) };

12. 发布你的类型工具库

当你对自己的类型工具库感到满意时,可以考虑将其发布为npm包:

  1. 创建一个types.ts文件包含所有类型定义
  2. 添加适当的类型导出
  3. 编写详细的文档和示例
  4. 配置package.json中的类型定义入口
// types.ts export * from './promise-utils'; export * from './function-utils'; export * from './object-utils'; // ...其他工具类别
// package.json { "name": "ts-type-utils", "version": "1.0.0", "types": "./dist/types.d.ts", // ...其他配置 }
http://www.jsqmd.com/news/692527/

相关文章:

  • 时间序列建模避坑指南:你的AR模型真的‘平稳’吗?从统计性质反推参数设置
  • VSCode医疗数据校验速成课:3个插件+4类规则+1套CI/CD流程,今天就能上线合规校验
  • 深度伪造技术革命:roop-unleashed 架构解析与工程实践
  • 微信聊天记录永久保存:3步掌握WeChatMsg免费本地备份方案
  • Diablo Edit2:3步掌握暗黑破坏神2角色编辑终极指南,告别重复刷装备
  • 机器人会突然“死机”吗?坏了谁来修?多久能修好?
  • 深度学习核心架构与工业实践指南
  • 3D打印爱好者的福音:手把手教你用3DMAX插件生成可打印的螺母螺栓(含间隙设置)
  • Python自动化下载新思路:Aria2 JSON-RPC配置与调用避坑指南(CentOS/Windows通用)
  • 从‘tf.contrib.rnn‘到‘tf.nn.rnn_cell‘:TensorFlow 2.x里那些被‘搬家‘的API都去哪儿了?
  • ARM MCU-制作Linux rootfs
  • FPGA时钟设计避坑指南:以紫光PGL22G的PLL为例,聊聊IP核配置的那些细节
  • 3个场景彻底解决Windows风扇噪音:FanControl智能散热管理实战指南
  • 从PCIe到NVMe:为什么你的SSD必须实现这6个Capability?一次讲清硬件兼容性
  • LaTeX数学公式到Word的技术迁移方案:MathJax与OMML的桥接实现
  • 如何高效管理Navicat试用期:macOS平台终极解决方案指南
  • 在线3D模型查看器:5个简单步骤快速上手浏览器端3D可视化
  • 2026年论文AI率超90%怎么办?亲测实用的四款工具,最后一款必收藏 - 降AI实验室
  • 成人如何挑选优质维生素D3?2026十大权威维生素D3榜单,助力钙质吸收强健骨骼 - 博客万
  • AutoDock Vina终极指南:5分钟学会分子对接的免费开源神器
  • 等保三级合规:企业级智能体全链路数据安全落地方案 —— 2026年企业级AI Agent安全架构实战
  • 中电金信X四川农商银行打造分布式核心系统建设样板
  • 用Pandas搞定股票每日收益率计算:从简单收益率到对数收益率,新手避坑指南
  • API攻防-接口类型SOAPOpenAPI导入项目识别WSDL解析JSON解析联动扫描器
  • 别再傻傻分不清!一张图看懂宝马底盘代号E、F、G、U系列的区别与演变
  • 如何快速实现微信自动化:wxauto工具的完整使用指南
  • 别再瞎调了!用MATLAB的Bayesopt工具箱给XGBoOST自动调参,效率提升10倍
  • 2026洛阳商务宴请与江浙菜定制:诱江南官方电话+深度品牌横评避坑指南 - 优质企业观察收录
  • 从零手写C++ MCP网关:2小时搭建支持100万并发连接的轻量级架构原型(含完整ASIO+RingBuffer+FlatBuffers代码骨架),现在不学,下次大促你就得通宵改bug!
  • 5个理由告诉你:为什么Formily是构建复杂表单的终极解决方案!