ReturnType
是怎么实现的?
当你在使用 TypeScript 一段时间后,你可能会使用一些技巧来追求比较优雅的类型推断。如果你用过 TypeScript 内置工具类型 的话,你应该会好奇ReturnType
是如何实现的?
type T0 = ReturnType<() => string>;
// string
实际上它并不神秘,你可以在这里找到所有内置工具类型的实现,如果你不想直面源码,这里还有一篇源码阅读笔记,可以找到 ReturnType
的实现如下:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
注意这里出现了 infer
关键词,官方对 infer
的解释是这样的:
Within the
extends
clause of a conditional type, it is now possible to haveinfer
declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have multipleinfer
locations for the same type variable.
翻译过来就是:
infer
关键词常在条件类型中和 extends
关键词一同出现,表示将要推断的类型,作为类型变量可以在三元表达式的 True 部分引用。而 ReturnType
正是使用这种方式提取到了函数的返回类型。
infer
的常见使用方式
简单的类型提取
type Unpacked<T> = T extends (infer U)[] ? U : T;
type T0 = Unpacked<string[]>; // string
type T1 = Unpacked<string>; // string
嵌套的按顺序类型提取
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
使用多个 infer
变量进行类型提取
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>; // string
type T11 = Foo<{ a: string, b: number }>; // string | number
一道面试题
题目如下:
interface Logger {
time: number;
asyncLog:(msg: string) => Promise<string>;
syncLog:(msg: string) => number;
}
type Translate<T> = /** 你需要实现的部分 **/;
// 要求 Translate
// 1. 提取出为函数类型的属性,丢弃掉其它类型的属性
// 2. 将函数返回类型调整为形参类型(假定有且只有一个参数)
// 实现效果如下:
type T0 = Translate<Logger>;
// 等价于
type T0 = {
// 其它属性被丢弃
asyncLog: (arg: string) => string;
// return 类型被调整为跟 arg 保持一致
syncLog: (arg: string) => string;
// return 类型被调整为跟 arg 保持一致
}
const result: T0 = {
asyncLog(msg: string) { return msg }
};
解题思路:
强烈建议你此时打开编辑器做好准备同我一起解决这个问题,这大概会花费你 10 分钟时间。
先实现一个类型可以提取出指定类型,用来筛选出所有为函数类型的属性
type FilterTypes<T, U> = {
[Key in keyof T]: T[Key] extends U ? Key : never
};
// 看看阶段性成果
type T = FilterTypes<Logger, Function>;
// type T = {
// time: never;
// syncLog: "syncLog";
// asyncLog: "asyncLog";
// }
剔除 never
,取出所有 key
type FilterKeys<T, U> = FilterTypes<T, U>[keyof T];
// 看看阶段性成果
type T = FilterKeys<Logger, Function>;
// type T = "syncLog" | "asyncLog"
使用 Pick
提取出子类型
type SubType<T, U> = Pick<T, FilterKeys<T, U>>;
// 看看阶段性成果,此时我们已经成功提取出了所有类型为函数的属性,满足要求
type T = SubType<Logger, Function>;
// type T = {
// syncLog: (msg: string) => number;
// asyncLog: (msg: string) => Promise<string>;
// }
使用 infer
将函数的返回类型改为形参类型
// 将参数类型作为返回类型
type ArgAsReturn<T> = {
[K in keyof T]: T[K] extends ((arg: infer U) => any) ? ((arg: U) => U): never;
}
// 我们最终得到了 Translate
type Translate = ArgAsReturn<SubType<Logger, Function>>;
// 看看最后效果,满足要求
type T = Translate<Logger>;
// type T0 = {
// asyncLog: (arg: string) => string;
// syncLog: (arg: string) => string;
// }
文中部分示例来自TypeScript 官网 - 高级类型,面试题灵感来自中国 LeetCode,原题太绕且有 Redux 倾向,因此做了简单改造,基本思路一致甚至更全面。