各位观众老爷,早上好!我是老码,今天给大家唠唠 TypeScript 里一个非常有趣,但也常常让人挠头的关键字:infer。 别看它只有五个字母,但它在类型推断的世界里可是个大杀器。 掌握了它,你的 TypeScript 类型体操水平,绝对能上一个新台阶。
今天咱们就以讲座的形式,深入浅出地剖析 infer 的用法,保证各位听完之后,都能把它玩得溜溜的。
一、啥是 infer?
首先,我们得明白 infer 是干啥的。 简单来说,infer 是 TypeScript 中用于 类型推断 的一个关键字。 它的作用是:在条件类型中,允许我们声明一个类型变量,并让 TypeScript 自动推断出该变量的类型。
有点绕是吧? 没关系,咱们慢慢来。
想象一下,你是一位侦探,需要根据一些线索(类型条件)来推断出嫌疑人(类型变量)的身份。 infer 就相当于你手中的放大镜,帮助你从线索中提取出关键信息。
二、infer 的基本语法
infer 总是出现在条件类型中,它的基本语法是这样的:
type MyType<T> = T extends SomeType<infer U> ? TypeIfTrue : TypeIfFalse;
解释一下:
MyType<T>: 这是一个泛型类型,接受一个类型参数T。T extends SomeType<infer U>: 这是一个条件类型。 它检查类型T是否可以赋值给SomeType<infer U>。infer U: 这是infer的关键所在。 它声明了一个类型变量U, TypeScript 会尝试从SomeType<infer U>中推断出U的类型。TypeIfTrue: 如果T可以赋值给SomeType<infer U>,那么MyType<T>的类型就是TypeIfTrue。 在这里,你就可以使用U这个推断出来的类型了。TypeIfFalse: 如果T不能赋值给SomeType<infer U>,那么MyType<T>的类型就是TypeIfFalse。
三、infer 的常见应用场景
光说理论有点枯燥,咱们来看几个实际的例子,看看 infer 在哪些地方能大显身手。
1. 获取函数返回值类型
这是 infer 最常见的用法之一。 假设我们有一个函数类型,想知道它的返回值类型是什么,就可以用 infer 来推断。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>; // AddReturnType 的类型是 number
解释一下:
ReturnType<T extends (...args: any) => any>:ReturnType接收一个函数类型T作为参数。T extends (...args: any) => any确保T是一个函数类型。T extends (...args: any) => infer R ? R : any: 这里使用了条件类型和infer。 如果T是一个函数类型,那么infer R就会推断出返回值类型,并将其赋值给类型变量R。 然后,ReturnType的类型就是R。 如果T不是一个函数类型,那么ReturnType的类型就是any。
2. 获取构造函数参数类型
类似于获取函数返回值类型,我们也可以用 infer 来获取构造函数的参数类型。
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
class Person {
constructor(name: string, age: number) {
// ...
}
}
type PersonConstructorParams = ConstructorParameters<typeof Person>; // PersonConstructorParams 的类型是 [string, number]
解释一下:
ConstructorParameters<T extends new (...args: any) => any>:ConstructorParameters接收一个构造函数类型T作为参数。T extends new (...args: any) => any确保T是一个构造函数类型。T extends new (...args: infer P) => any ? P : never: 如果T是一个构造函数类型,那么infer P就会推断出构造函数的参数类型,并将其赋值给类型变量P。 然后,ConstructorParameters的类型就是P。 如果T不是一个构造函数类型,那么ConstructorParameters的类型就是never。
3. 获取数组元素的类型
infer 还可以用来获取数组元素的类型。
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type NumberArray = number[];
type StringArray = string[];
type NumberElementType = ArrayElementType<NumberArray>; // NumberElementType 的类型是 number
type StringElementType = ArrayElementType<StringArray>; // StringElementType 的类型是 string
解释一下:
ArrayElementType<T>:ArrayElementType接收一个类型T作为参数。T extends (infer U)[] ? U : never: 如果T是一个数组类型,那么infer U就会推断出数组元素的类型,并将其赋值给类型变量U。 然后,ArrayElementType的类型就是U。 如果T不是一个数组类型,那么ArrayElementType的类型就是never。
4. 获取 Promise 的 resolve 类型
有时候,我们需要知道一个 Promise resolve 的类型, infer 也能帮我们做到。
type PromiseResolveType<T> = T extends Promise<infer U> ? U : never;
async function fetchData(): Promise<string> {
return "Data fetched!";
}
type FetchDataResolveType = PromiseResolveType<ReturnType<typeof fetchData>>; // FetchDataResolveType 的类型是 string
解释一下:
PromiseResolveType<T>:PromiseResolveType接收一个类型T作为参数。T extends Promise<infer U> ? U : never: 如果T是一个 Promise 类型,那么infer U就会推断出 Promise resolve 的类型,并将其赋值给类型变量U。 然后,PromiseResolveType的类型就是U。 如果T不是一个 Promise 类型,那么PromiseResolveType的类型就是never。
5. 模式匹配 (Pattern Matching)
infer 还可以用于更复杂的模式匹配,比如提取元组类型中的特定元素。
type ExtractSecondElement<T extends any[]> = T extends [any, infer Second, ...any[]] ? Second : never;
type MyTuple = [number, string, boolean];
type SecondElementType = ExtractSecondElement<MyTuple>; // SecondElementType 的类型是 string
解释一下:
ExtractSecondElement<T extends any[]>:ExtractSecondElement接收一个数组类型T作为参数。T extends [any, infer Second, ...any[]] ? Second : never: 这里使用了模式匹配。 如果T是一个至少包含两个元素的元组类型,那么infer Second就会推断出第二个元素的类型,并将其赋值给类型变量Second。 然后,ExtractSecondElement的类型就是Second。 如果T不是一个符合条件的元组类型,那么ExtractSecondElement的类型就是never。
四、infer 的高级用法与注意事项
上面的例子都是 infer 的基本用法。 实际上,infer 还可以玩出更多花样。
1. 多个 infer 的使用
在某些情况下,你可能需要在同一个条件类型中使用多个 infer。 例如,提取函数参数类型和返回值类型。
type FunctionTypeInfo<T extends (...args: any) => any> = T extends (...args: infer Args) => infer Return ? {
args: Args;
return: Return;
} : never;
function myFunc(a: number, b: string): boolean {
return a > b.length;
}
type MyFuncInfo = FunctionTypeInfo<typeof myFunc>;
// MyFuncInfo 的类型是 { args: [number, string]; return: boolean; }
在这个例子中,我们同时使用了 infer Args 来推断参数类型,以及 infer Return 来推断返回值类型。
2. infer 与递归类型
infer 还可以与递归类型结合使用,构建更强大的类型操作。 例如,将一个嵌套的 Promise 类型展平到最内层的 resolve 类型。
type DeepPromiseResolve<T> = T extends Promise<infer U> ? DeepPromiseResolve<U> : T;
async function nestedPromise(): Promise<Promise<Promise<number>>> {
return Promise.resolve(Promise.resolve(Promise.resolve(123)));
}
type NestedPromiseResolveType = DeepPromiseResolve<ReturnType<typeof nestedPromise>>; // NestedPromiseResolveType 的类型是 number
3. infer 的位置很重要
infer 出现的位置非常重要,它决定了 TypeScript 如何进行类型推断。 一定要确保 infer 出现在你想要推断的类型的位置。
4. 小心 any
如果 TypeScript 无法推断出 infer 的类型,它会默认使用 any。 这可能会导致类型安全问题,所以要尽量避免这种情况。 可以通过添加更严格的类型约束,或者提供默认类型来避免 any 的出现。
五、infer 的实际案例分析
为了让大家更好地理解 infer 的应用,我们来看一个更复杂的实际案例:实现一个 PickByType 工具类型,它可以从一个对象类型中选取指定类型的属性。
type PickByType<T, Value> = {
[K in keyof T as T[K] extends Value ? K : never]: T[K];
};
interface Person {
name: string;
age: number;
address: string;
isEmployed: boolean;
}
type StringProps = PickByType<Person, string>; // StringProps 的类型是 { name: string; address: string; }
解释一下:
PickByType<T, Value>:PickByType接收两个类型参数:T是对象类型,Value是要选取的属性类型。[K in keyof T as T[K] extends Value ? K : never]: 这里使用了映射类型和条件类型。 它遍历T的所有属性键K。T[K] extends Value ? K : never: 这是一个条件类型。 它检查属性T[K]的类型是否可以赋值给Value。 如果可以,那么保留属性键K;否则,将属性键K设置为never,表示排除该属性。T[K]: 如果属性键K被保留,那么PickByType的类型就是T[K]。
这个例子展示了 infer 在类型体操中的强大能力。 通过巧妙地使用 infer 和条件类型,我们可以实现各种复杂的类型操作。
六、总结
infer 是 TypeScript 中一个非常重要的关键字,它允许我们在条件类型中进行类型推断,从而实现更灵活、更强大的类型操作。 掌握 infer 的用法,可以帮助你编写更健壮、更可维护的 TypeScript 代码。
希望今天的讲座能帮助大家更好地理解 infer。 记住,熟能生巧,多练习、多实践,你就能真正掌握 infer 的精髓,成为 TypeScript 类型体操高手!
好了,今天的分享就到这里,感谢各位的观看,下课!