各位观众老爷,早上好!我是老码,今天给大家唠唠 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 类型体操高手!
好了,今天的分享就到这里,感谢各位的观看,下课!