- https://github.com/type-challenges/type-challenges
- https://wangtunan.github.io/blog/typescript/challenge.html
- https://juejin.cn/post/7132490947320872974
- https://juejin.cn/post/7000360236372459527
- https://zhuanlan.zhihu.com/p/464558626
遍历
在 Typescript 类型中,通常用 extends
和 infer
提取元素,用递归实现逐个提取。
我们可以通过内置工具类型 ReturnType
理解这种类型提取:
ts
type ReturnType<T> = T extends (...args: any) => infer R ? R : any;
infer
表示从这个位置推导出类型,并赋给 R
参数,也就是提取类型。
示例格式
假定你需要遍历一个类型,并对其每个元素进行操作,就像 Array.prototype.map
一样。
ts
/**@template T 可遍历类型 */
type Each<T> = ...
/**@template T 元素类型 */
type Action<T> = ...
字符串
ts
type Each<T, S> = T extends `${infer L}${infer R}` ? Each<R, Action<L>> : S;
字符串可以通过 `${infer L}${infer R}`
提取第一个字符 L
,而 R
表示剩下的字符串。
- 如果
T
为空字符,则判断为假,直接返回S
- 如果
T
为单字符,则R
为空字符
元组
ts
type Each<T, S> = T extends [infer L, ...infer R] ? Each<R, Action<L>> : S;
元组遍历比字符串的更灵活,也可以从尾部开始遍历。
联合类型
ts
type Each<T> = T extends any ? Action<T> : never;
联合类型可以直接使用 extends
遍历,但这只在类型参数上有效:
ts
type Arg = 0 | 1;
type Fn = Arg extends any ? (e: Arg) => void : never; //(e: 0 | 1) => void
在模板字符串中,联合类型将被自动遍历:
ts
type T = `${0 | 1}`; //'0' | '1'
补充
有时我们还需要逐个提取联合类型的元素,可以看看 UnionToTuple
是怎么实现的。
类型转换
UnionToIntersection
ts
/**联合类型转交叉类型 */
type UnionToIntersection<T> = (T extends any ? (e: T) => void : never) extends (
e: infer U,
) => void
? U
: never;
UnionToTuple
ts
/**获取联合类型最后一个元素 */
type Last<T> =
UnionToIntersection<T extends any ? (e: T) => void : never> extends (
e: infer U,
) => void
? U
: never;
/**联合类型转元组 */
type UnionToTuple<T, S extends any[] = []> =
Last<T> extends infer U
? U extends never
? S
: UnionToTuple<Exclude<T, U>, [U, ...S]>
: never;