TypeScript类型体操手把手教你用infer实现一个简易的‘类型提取’工具库当你第一次看到infer关键字时可能会觉得它像TypeScript类型系统中的某种黑魔法。但别担心它实际上是一种强大的类型推导工具能让我们在类型层面实现类似模式匹配的操作。今天我们就来一起动手用infer构建一个实用的类型工具库就像type-fest或utility-types那样。1. 理解infer的基础概念infer是TypeScript中用于条件类型推导的关键字它允许我们在类型系统中提取或捕获其他类型。想象一下你有一个包裹在Promise中的类型而你想知道这个Promise最终会返回什么——这就是infer的用武之地。type UnpackPromiseT T extends Promiseinfer U ? U : never;这个简单的类型工具可以提取Promise的泛型参数。让我们看个实际例子type User { name: string; age: number }; type UserPromise PromiseUser; type UnpackedUser UnpackPromiseUserPromise; // { name: string; age: number }关键点infer只能在extends子句的条件类型中使用它创建了一个临时的类型变量上例中的U当匹配成功时这个临时变量会被赋值为匹配到的类型2. 构建基础类型提取工具现在让我们扩展这个基础概念构建一些更实用的类型工具。2.1 提取函数返回类型type GetReturnTypeT T extends (...args: any[]) infer R ? R : never; function getUser(): User { return { name: Alice, age: 30 }; } type UserReturnType GetReturnTypetypeof getUser; // User2.2 提取数组元素类型type ArrayElementT T extends (infer U)[] ? U : never; type Numbers number[]; type NumberElement ArrayElementNumbers; // number2.3 处理嵌套Promise有时候我们会遇到多层嵌套的Promise这时候可以结合递归来解决type DeepUnpackPromiseT T extends Promiseinfer U ? DeepUnpackPromiseU : T; type DeepPromise PromisePromisePromiseUser; type DeepUnpacked DeepUnpackPromiseDeepPromise; // User3. 高级infer技巧3.1 协变与逆变infer在不同位置会有不同的行为这被称为协变和逆变// 协变示例 - 返回联合类型 type ExtractPropertiesT T extends { name: infer U, age: infer U } ? U : never; const user { name: Bob, age: 25 }; type UserProps ExtractPropertiestypeof user; // string | number // 逆变示例 - 返回交叉类型 type ExtractParamsT 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 FirstElementT extends any[] T extends [infer First, ...any[]] ? First : never; type RestElementsT extends any[] T extends [any, ...infer Rest] ? Rest : never; type Tuple [string, number, boolean]; type First FirstElementTuple; // string type Rest RestElementsTuple; // [number, boolean]4. 构建完整类型工具库现在让我们把这些工具组合起来创建一个更完整的类型工具库type TypeUtils { // Promise相关 PromiseType: T(p: T) T extends Promiseinfer U ? U : never; DeepPromiseType: T(p: T) T extends Promiseinfer U ? DeepPromiseTypeU : T; // 函数相关 Parameters: T extends (...args: any) any(fn: T) ParametersT; ReturnType: T extends (...args: any) any(fn: T) ReturnTypeT; // 数组相关 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) FirstElementT; Rest: T extends any[](tuple: T) RestElementsT; }; // 使用示例 declare const utils: TypeUtils; const userPromise Promise.resolve({ name: Charlie, age: 40 }); type UnpackedUser typeof utils.PromiseTypetypeof userPromise; // { name: string; age: number }5. 实战应用场景5.1 API响应处理在处理API响应时我们经常需要处理Promise包装的数据async function fetchUser(): Promise{ data: User } { // API调用 } type ApiResponse typeof utils.ReturnTypetypeof fetchUser; // Promise{ data: User } type UserData typeof utils.PromiseTypeApiResponse[data]; // User5.2 高阶组件类型在React中我们可以用这些工具来增强高阶组件的类型安全function withUserP(Component: React.ComponentTypeP { user: User }) { return function WrappedComponent(props: P) { const [user] React.useStateUser({ name: Dave, age: 50 }); return Component {...props} user{user} /; }; } type OriginalProps { id: string }; const EnhancedComponent withUserOriginalProps(function Component({ id, user }) { return div{id}: {user.name}/div; }); type WrappedProps typeof utils.Parameterstypeof EnhancedComponent[0]; // OriginalProps5.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层使用类型别名缓存中间结果这可以帮助编译器优化类型检查优先使用内置工具类型如Parameters、ReturnType等它们已经过优化// 不推荐 - 每次使用时都会重新计算 type LongChainT /* 复杂的类型运算 */; // 推荐 - 预先计算并缓存 type Step1 /* 部分计算 */; type Step2 /* 基于Step1的进一步计算 */; type FinalResult /* 基于Step2的最终结果 */;7. 扩展工具库让我们再添加一些实用的类型工具type FlattenArrayT T extends (infer U)[] ? FlattenArrayU : T; type DeepReadonlyT { readonly [K in keyof T]: T[K] extends object ? DeepReadonlyT[K] : T[K]; }; type OptionalPropsT, K extends keyof T OmitT, K PartialPickT, K; type RequirePropsT, K extends keyof T OmitT, K RequiredPickT, K;这些工具在处理复杂对象类型时特别有用type NestedArray number[][][]; type FlatNumber FlattenArrayNestedArray; // number type ComplexObject { a: string; b: { c: number; d: boolean[]; }; }; type ReadonlyComplex DeepReadonlyComplexObject;8. 测试你的类型工具为了确保我们的类型工具工作正常我们可以使用一些类型断言技巧type AssertEqualT, U [T] extends [U] ? [U] extends [T] ? true : false : false; // 测试用例 type Test1 AssertEqual typeof utils.PromiseTypePromisestring, string ; // true type Test2 AssertEqual typeof utils.ArrayElementstring[], string ; // true type Test3 AssertEqual typeof utils.First[string, number, boolean], string ; // true9. 处理边缘情况好的类型工具应该能够优雅地处理各种边缘情况// 处理never和any type SafeUnpackT [T] extends [Promiseinfer U] ? U : T; type TestNever SafeUnpacknever; // never type TestAny SafeUnpackany; // any // 处理联合类型 type UnpackUnionT T extends Promiseinfer U ? U : T; type UnionTest UnpackUnionPromisestring | number; // string | number10. 与TypeScript新特性结合我们可以将infer与TypeScript的新特性如模板字面量类型结合type ExtractRouteParamsT 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 ApiMethodReq, Res (request: Req) PromiseRes; type ApiClientEndpoints { [K in keyof Endpoints]: Endpoints[K] extends ApiMethodinfer Req, infer Res ? (request: Req) PromiseRes : never; }; type MyApi { getUser: ApiMethod{ id: string }, User; createUser: ApiMethodOmitUser, id, { id: string }; }; const client: ApiClientMyApi { getUser: async ({ id }) ({ name: Eve, age: 60 }), createUser: async (user) ({ ...user, id: 123 }) };12. 发布你的类型工具库当你对自己的类型工具库感到满意时可以考虑将其发布为npm包创建一个types.ts文件包含所有类型定义添加适当的类型导出编写详细的文档和示例配置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, // ...其他配置 }