用真实开发场景解锁TypeScript infer关键字的实战价值TypeScript的infer关键字就像一把瑞士军刀——看起来简单但只有真正用起来才知道它的强大。很多开发者对泛型和条件类型能说出一二可一旦遇到infer就陷入知道每个字但连起来就不懂的困境。今天我们不谈晦涩的理论直接通过你明天就可能遇到的开发场景看看infer如何成为你的类型编程利器。1. 从API响应中精确提取数据类型现代前端开发中处理API响应是家常便饭。假设我们有一个返回PromiseUser的接口interface User { id: number; name: string; email: string; } declare function fetchUser(): PromiseUser;现在需要定义一个类型工具能够提取出Promise包裹的实际类型。这就是infer的经典应用场景type UnpackPromiseT T extends Promiseinfer R ? R : never; // 使用示例 type UserType UnpackPromiseReturnTypetypeof fetchUser; // 等价于 type UserType User这个简单的类型工具背后有几个关键点extends用于条件类型判断infer R声明了一个类型变量R它会自动推断出Promise包裹的类型整个表达式可以理解为如果T是Promise类型就返回它包裹的类型R否则返回never实际应用场景扩展当你的项目使用GraphQL时返回的数据结构往往是Promise{ data: T }我们可以轻松扩展这个工具type UnpackGraphQLResponseT T extends Promise{ data: infer R } ? R : never;2. 深度解构复杂嵌套对象类型现实项目中的数据结构往往比教程中的例子复杂得多。考虑这样一个多层嵌套的用户信息type ComplexUser { id: number; info: { basic: { name: string; age: number; }; contact: { email: string; phone?: string; }; }; meta: Recordstring, any; };2.1 安全访问深层属性类型假设我们需要安全地获取info.basic.name的类型可以这样实现type GetDeepTypeT, K extends string[] K extends [infer First, ...infer Rest] ? First extends keyof T ? GetDeepTypeT[First], Rest extends string[] ? Rest : [] : never : T; // 使用示例 type UserNameType GetDeepTypeComplexUser, [info, basic, name]; // 等价于 type UserNameType string这个工具类型的工作原理使用infer解构路径数组递归地深入对象类型在每一步都进行类型安全检查2.2 提取对象中所有函数类型的参数在大型应用中我们有时需要统一处理某些特定类型的属性。比如提取所有函数类型的参数type FunctionParamsT { [K in keyof T]: T[K] extends (...args: infer P) any ? P : never; }[keyof T]; // 使用示例 type ApiMethods { getUser: (id: number) User; searchUsers: (query: string, limit?: number) User[]; }; type AllParams FunctionParamsApiMethods; // 等价于 type AllParams [number] | [string, (number | undefined)?]这个技巧在创建统一API客户端时特别有用可以确保类型安全贯穿整个应用。3. 函数类型的高级模式匹配3.1 提取函数返回类型React生态中经常需要处理各种异步操作和hooks了解函数返回类型很有必要type ReturnTypeT T extends (...args: any[]) infer R ? R : any; // 实际应用获取自定义hook的返回类型 function useUserSettings() { return { theme: dark, notifications: true, // ...其他配置 }; } type Settings ReturnTypetypeof useUserSettings;3.2 处理重载函数当遇到函数重载时infer的行为会有些特殊function stringOrNumber(value: string): number; function stringOrNumber(value: number): string; function stringOrNumber(value: string | number): string | number { /* 实现 */ } type OverloadedReturnT T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2 } ? [A1, R1] | [A2, R2] : T extends (...args: infer A) infer R ? [A, R] : never; type Test OverloadedReturntypeof stringOrNumber; // 等价于 type Test [value: string, number] | [value: number, string]4. 类型安全的Redux Action工具在Redux或类似状态管理中我们经常需要处理各种action。使用infer可以创建类型安全的工具type ActionT extends string, P void P extends void ? { type: T } : { type: T; payload: P }; type ExtractActionPayloadA, T A extends ActionT, infer P ? P : never; // 定义一组action type UserActions | ActionUSER_LOAD, { id: number } | ActionUSER_UPDATE, PartialUser | ActionUSER_RESET; // 使用示例 type LoadPayload ExtractActionPayloadUserActions, USER_LOAD; // 等价于 type LoadPayload { id: number } type ResetPayload ExtractActionPayloadUserActions, USER_RESET; // 等价于 type ResetPayload void这个模式可以扩展到整个Redux生态类型安全的action creators精确的reducer参数类型自动完成的dispatch调用5. 处理联合类型和交叉类型infer在协变和逆变位置的表现不同这是TypeScript类型系统中最微妙的部分之一。5.1 协变位置的行为type ExtractPropertyTypesT T extends { a: infer U, b: infer U } ? U : never; type Example1 ExtractPropertyTypes{ a: string, b: number }; // 等价于 type Example1 string | number这里infer U在协变位置读取数据的位置所以TypeScript会推断为联合类型。5.2 逆变位置的行为type ExtractParameterTypesT T extends { a: (arg: infer U) void, b: (arg: infer U) void } ? U : never; type Example2 ExtractParameterTypes{ a: (arg: string) void, b: (arg: number) void }; // 等价于 type Example2 string number (即never)在逆变位置写入数据的位置TypeScript会推断为交叉类型。这就是为什么函数参数类型会表现出这样的行为。6. 构建类型安全的API组合工具在实际项目中我们经常需要组合多个API调用。使用infer可以创建类型安全的组合工具type ExtractApiResponseT T extends (...args: any[]) Promiseinfer R ? R : T; type ComposeApisT extends [...any[]] T extends [infer First, ...infer Rest] ? [ExtractApiResponseFirst, ...ComposeApisRest] : []; // 使用示例 declare function getPosts(): PromisePost[]; declare function getComments(): PromiseComment[]; declare function getUser(): PromiseUser; type CombinedData ComposeApis[typeof getPosts, typeof getComments, typeof getUser]; // 等价于 type CombinedData [Post[], Comment[], User]这个模式可以扩展到更复杂的场景并行请求组合请求依赖管理响应数据转换7. 类型安全的装饰器模式在实现装饰器时保持类型信息非常重要type DecoratedMethodT T extends (...args: infer P) infer R ? (originalMethod: (...args: P) R, context: any) (...args: P) R : never; function logM extends (...args: any[]) any(target: M): DecoratedMethodM { return function(originalMethod: M, context: ClassMethodDecoratorContext) { return function(...args: ParametersM) { console.log(Calling ${context.name as string} with, args); const result originalMethod.apply(this, args); console.log(Result:, result); return result; }; }; } class Example { log add(a: number, b: number): number { return a b; } }这个装饰器完美保留了原始方法的类型信息包括参数和返回类型。