本文对 11 个Ts 类型体操进行专项训练,喜欢的朋友可以收藏起来今后翻看。
1. Anyof
在类型系统中实现类似于 Python 中 any 函数。类型接收一个数组,如果数组中任一个元素为真,则返回 true,否则返回 false。如果数组为空,返回 false。
例如:
type Sample1 = AnyOf<[1, '', false, [], {}]>; // expected to be true.
type Sample2 = AnyOf<[0, '', false, [], {}]>; // expected to be false.
自写答案
type AnyOf<T extends any[]> = T extends [infer A, ...infer B] ? (
A extends any[] ? (
A["length"] extends 0 ? AnyOf<B> : true
) : (A extends object ? (
[keyof A] extends [never] ? AnyOf<B> : true
): (A extends (0|""|false|null|undefined|[]) ? AnyOf<B> : true))
) : false;
答案
type AnyOf<T extends readonly any[]> = T[number] extends
| ''
| 0
| []
| false
| { [P in any]: never }
? false
: true;
使用 { [P in any]: never } 表示 {}, 使用 object 表示一般的对象,但是 object 包含所有数组。
不得不说,答案还是巧妙!
2. NumberRange
有时我们需要限制数字的范围...
示例:
type result = NumberRange<2 , 9> // | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
自写答案
type BuildArray<T extends number, K extends unknown[] = []> = {
0: K,
1: BuildArray<T, [...K, unknown]>
}[
K["length"] extends T ? 0 : 1
]
type NumberRange<A extends number, B extends number, C extends unknown[] = BuildArray<A>, D extends any[] = []> = {
0: D[number],
1: NumberRange<A, B, [...C, unknown], [...D, C["length"]]>
}[
C["length"] extends [...BuildArray<B>, unknown]["length"] ? 0 : 1
]
答案
type NumberRange<
L extends number,
H extends number,
CArr extends any[] = [],
OArr extends unknown[] = [unknown],
R extends number = H
> = H extends CArr['length']
? R
: L extends CArr['length']
? NumberRange<OArr['length'], H, [any, ...CArr], [unknown, ...OArr], L | R>
: NumberRange<L, H, [...CArr, any], [unknown, ...OArr]>;
下面的这两种写法,后面的可读性强一些,但是安全性不如前面那个:
type BuildArray2<T extends number, K extends any[] = []> = {
0: K,
1: BuildArray<T, [any, ...K]>
}[
K["length"] extends T ? 0 : 1
]
type BuildArray<T extends number, K extends any[] = []> = K["length"] extends T ? K : BuildArray<T, [any, ...K]>;
3. ConstructTuple
构造一个给定长度的元组。
例如
type result = ConstructTuple<2> // 期望得到 [unknown, unkonwn]
自写答案
type ConstructTuple<T extends number, K extends any[] = []> = K["length"] extends T ? K : BuildArray<T, [any, ...K]>;
答案
type ConstructTuple<
L extends number,
K extends any[] = []
> = K['length'] extends L ? K : ConstructTuple<L, [unknown, ...K]>;
4. MapTypes
实现 MapTypes<T, R>, 它将对象 T 中的类型转换为有如下结构的类型 R 定义的类型
type StringToNumber = {
mapFrom: string; // 类型为 string 的键(key)
mapTo: number; // 会被转换为 number
};
MapTypes<{ iWillBeANumberOneDay: string }, StringToNumber>; // 结果 { iWillBeANumberOneDay: number; }
自写答案
type MapTypes<T extends object, K extends {
mapFrom: any;
mapTo: any;
}> = {
[P in keyof T] : T[P] extends K["mapFrom"] ? K["mapTo"] : T[P]
}
答案
type MapTypes<T, R extends { mapFrom: unknown; mapTo: unknown }> = {
[P in keyof T]: T[P] extends R['mapFrom']
? R extends R
? Equal<T[P], R['mapFrom']> extends true
? R['mapTo']
: never
: never
: T[P];
};
为什么包了两层?
R extends R
Equal<T[P], R['mapFrom']> extends true
推测可能是第二种写法的 T 没有限制为 object.
5. Unique
实现类型的 Lodash.uniq, Unique 接受数组 T, 返回没有重复值的数组 T
type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3]
type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7]
type Res2 = Unique<[1, 'a', 2, 'b', 2, 'a']>; // expected to be [1, "a", 2, "b"]
type Res3 = Unique<[string, number, 1, 'a', 1, string, 2, 'b', 2, number]>; // expected to be [string, number, 1, "a", 2, "b"]
type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
自写答案
type IsIn<T, K extends any[] = []> = K extends [infer A, ...infer B] ? (
(<F>() => F extends A ? 1 : 0) extends (<F>() => F extends T ? 1 : 0) ? true : IsIn<T, B>
) : false;
type Unique<T extends any[], K extends any[] = []> = T extends [infer A, ...infer B] ? (
IsIn<A, K> extends true ? Unique<B, K> : Unique<B, [...K, A]>
) : K;
答案
type Contains<TArr extends any[], T> = TArr extends [infer L, ...infer R]
? (<F>() => F extends L ? 1 : 0) extends <F>() => F extends T ? 1 : 0
? true
: Contains<R, T>
: false;
type Unique<T extends any[], O extends any[] = []> = T extends [
infer L,
...infer R
]
? Contains<O, L> extends true
? Unique<R, O>
: [L, ...Unique<R, [...O, L]>]
: [];
这个题的难点在于如何证明两个类型是严格相等的,也就是不仅能够判断出 'a' 和 'a' 相等,还要判断出 'a' 和 string 不相等,这里提供一种借用函数的方式:
type IsEqual<A, B> = (<F>() => F extends A ? 1 : 0) extends (<F>() => F extends B ? 1 : 0) ? true : false;
我们将其稍作改动即可得到能够判断 never 的 IsNever 或者 IsUnknown 或者其他。
type IsEqual<T> = Equal<T, never>
6. LastIndexOf
实现类型的 Array.lastIndexOf, LastIndexOf<T, U> 接受泛型参数 Array T 和 any U 并返回数组 T 中最后一个 U 的索引
示例:
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2>; // 3
type Res2 = LastIndexOf<[0, 0, 0], 2>; // -1
自写答案
type IsEqual<A, B> = (<F>() => F extends A ? 1 : 0) extends (<F>() => F extends B ? 1 : 0) ? true : false;
type LastIndexOf<T extends any[], K> = T extends [...infer A, infer B] ? (
IsEqual<B, K> extends true ? A["length"] : LastIndexOf<A, K>
) : -1;
答案
type LastIndexOf<T extends any[], U> = T extends [...infer L, infer R]
? (<F>() => F extends R ? 1 : 0) extends <F>() => F extends U ? 1 : 0
? L['length']
: LastIndexOf<L, U>
: -1;
IsEqual 简直神器。
7. Join
实现类型版本的 Array.join, Join<T, U> 接受数组 T 和 string 或者 number 类型 U 作为泛型参数, 并返回 U 连接数组 T 后的字符串.
自写答案
type CanInTemplate = string | number | bigint | boolean | null | undefined;
type Join<T extends unknown[], U extends string | number, C extends string = ''> = T extends [infer A, ...infer B] ? (
A extends CanInTemplate ? Join<B, U, `${C}${U}${A}`> : C extends `${U}${infer Re}` ? Re : never
) : (C extends `${U}${infer Re}` ? Re : never)
答案
type Stringify = string | number | bigint | boolean | null | undefined;
type Join<
T extends Stringify[],
U extends Stringify,
S extends Stringify = ''
> = T extends [infer L extends string, ...infer R extends string[]]
? Join<R, U, '' extends S ? `${L}` : `${S}${U}${L}`>
: S;
- 模板字符串允许的类型有 string | number | bigint | boolean | null | undefined;
- infer 之后还可以使用断言
T extends [infer L extends string, ...infer R extends string[]]
8. IndexOf
实现类型版本的 Array.indexOf, indexOf<T, U> 接受一个数组T和any类型的U作为参数, 返回T中第一个U的索引
type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
type Res1 = IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>; // expected to be 2
type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1
自写答案
type IsEqual<A, B> = (<F>()=>F extends A ? 0 : 1) extends (<F>()=>F extends B ? 0 : 1) ? true : false;
type IndexOf<T extends any[], R , C extends any[] = []> = T extends [infer A, ...infer B] ? (
IsEqual<A, R> extends true ? C["length"] : IndexOf<B, R, [...C, A]>
) : -1;
答案
type IndexOf<T extends any[], U extends any, S extends any[] = []> = T extends [
infer L,
...infer R
]
? true extends IsEqual<L, U>
? S['length']
: IndexOf<R, U, [...S, 0]>
: -1;
9. Trunc
实现类型版本的 Math.trunc, 其接受一个字符串或数字作为泛型参数, 并返回移除了全部小数位部分后的整数。
示例:
type A = Trunc<12.34>; // 12
自写答案
type Trunc<T extends number> = `${T}` extends `${infer R}.${any}` ? R : `${T}`;
答案
type Trunc<T extends string | number> = T extends number
? Trunc<`${T}`>
: T extends `${infer L}.${any}`
? L
: T;
这答案没有我写的利索。
10. Without
实现一个像 Lodash.without 函数一样的泛型 Without<T, U>,它接收数组类型的 T 和数字或数组类型的 U 为参数,会返回一个去除 U 中元素的数组 T。
例如:
type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []
自写答案
type IsEqual<A, B> = (<F>() => F extends A ? 1 : 0) extends <F>() => F extends B
? 1
: 0
? true
: false;
type _Without<T extends any[], U, K extends any[] = []> = T extends [
infer A,
...infer B
]
? IsEqual<A, U> extends true
? _Without<B, U, [...K]>
: _Without<B, U, [...K, A]>
: K;
type Without<
T extends any[],
U extends any[]|any,
K extends any[] = []
> = U extends any[] ? (
U extends [infer A, ...infer B] ? Without<_Without<T, A, K>, B, K> : T
) : _Without<T, U, K>
答案
type Without<T extends any[], U extends any[] | number> = U extends number
? Without<T, [U]>
: T extends [infer L, ...infer R]
? [
...(U extends [infer UL, ...infer UR]
? L extends UL
? []
: Without<[L], UR>
: [L]),
...Without<R, U>
]
: [];
感觉对于数组遍历的出口不是很清楚,所以写不出双循环。
11. TrimRight
实现 TrimRight<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串结尾的空白字符串。
例如
type Trimed = TrimRight<' Hello World '>; // 应推导出 ' Hello World'
自写答案
type TrimLeft<T extends string> = T extends `${infer A}${infer B}` ? (
A extends " "|"\r"|"\n" ? TrimLeft<B> : T
) : '';
type TrimRight<T extends string> = T extends `${infer A}${" "|"\r"|"\n"}` ? TrimRight<A> : T;
答案
type TrimRight<S extends string> = S extends `${infer L}${'\n' | '\t' | ' '}`
? TrimRight<L>
: S;
从左向右遍历容易,反过来难。
12. Fill
Fill 是 javascript 中常用的方法, 现在让我实现类型版本的 Fill
Fill<T, N, Start?, End?>, 正如你看到的那样, Fill接受四个泛型参数, 其中 T 和 N 是必填参数, Start 和 End 是可选参数
这些参数的要求如下: T 必须是一个元组(tuple), N 可以是任何值类型, Start 和 End 必须是大于或等于 0 的整数
自写答案
type BuildArray<T extends number, K extends unknown[] = []> = K["length"] extends T ? K : BuildArray<T, [...K, unknown]>;
type NumberRange<A extends number, B extends number, C extends unknown[] = BuildArray<A>, D extends any[] = []> = {
0: D[number],
1: NumberRange<A, B, [...C, unknown], [...D, C["length"]]>
}[
C["length"] extends [...BuildArray<B>, unknown]["length"] ? 0 : 1
]
type Fill<T extends any[], N, Start extends number = 0, End extends number = T["length"], Re extends any[] = []> = T extends [infer A, ...infer B] ? (
Fill<B, N, Start, End, Re["length"] extends NumberRange<Start, End> ? [...Re, N] : [...Re, A]>
) : Re;
答案
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
Pending extends any[] = [],
Filled extends any[] = []
> = T extends [infer L, ...infer R]
? [
Pending['length'] extends Start
? Filled['length'] extends End
? L
: N
: L,
...Fill<
R,
N,
Start,
End,
Pending['length'] extends Start ? Pending : [...Pending, 0],
Filled['length'] extends End ? Filled : [0, ...Filled]
>
]
: [];
我觉得有一些经常用的,可以背下来比如:NumberRange 或者 BuildArray.
13. Chunk
你知道 lodash 吗? lodash中有一个非常实用的方法Chunk, 让我们实现它吧.
Chunk<T, N>接受两个泛型参数, 其中 T 是 tuple 类型, N是大于 1 的数字
type exp1 = Chunk<[1, 2, 3], 2>; // expected to be [[1, 2], [3]]
type exp2 = Chunk<[1, 2, 3], 4>; // expected to be [[1, 2, 3]]
type exp3 = Chunk<[1, 2, 3], 1>; // expected to be [[1], [2], [3]]
自写答案
思路就是找一个池子往里面添加元素,满了之后以独立包装的形式添加到大池子中去。
type Chunk<T extends any[], K extends number, Cur extends any[] = [], Re extends any[] = []> = T extends [infer A, ...infer B] ? (
Cur['length'] extends K ? (
Chunk<T, K, [], [...Re, Cur]>
) : Chunk<B, K, [...Cur, A], [...Re]>
) : [...Re, Cur];
答案
type Chunk<
T extends any[],
N extends number,
S extends any[] = [],
ALL extends any[] = []
> = T extends [infer L, ...infer R]
? S['length'] extends N
? Chunk<T, N, [], [...ALL, S]>
: Chunk<R, N, [...S, L], ALL>
: S['length'] extends 0
? []
: [...ALL, S];
只能说一模一样。
14. IsTuple
实现 IsTuple 类型, 接受一个泛型参数 T 作为输入, 并返回 T 是否为 tuple 类型
示例:
type case1 = IsTuple<[number]>; // true
type case2 = IsTuple<readonly [number]>; // true
type case3 = IsTuple<number[]>; // false
自写答案
type IsTuple<T> = T extends (any[]|readonly any[]) ? (
T["length"] extends number ? (
number extends T["length"] ? false : true
) : false
) : false;
答案
/**
* 元组的长度是有限的,其`length`属性返回的是数字字面量;数组的`length`属性的类型是`number`
*/
type IsTuple<T> = [T] extends [never]
? false
: T extends readonly any[]
? number extends T['length']
? false
: true
: false;
注意:
readonly [number] extends readonly any[]为真;readonly [number] extends any[]为假。- 由于 Ts 中没有特定义类型 Tuple 故不能使用 IsEqual.
number[]["length"]的值为 number.number extends number为真,但是number extends 1为假。
15. GreaterThan
在本挑战中, 你需要实现 GreaterThan<T, U>, 它的作用像 T > U
你不需要考虑负数
示例:
GreaterThan < 2,
1 > //should be true
GreaterThan <
1,
1 > //should be false
GreaterThan <
10,
100 > //should be false
GreaterThan<111, 11>; //should be true
自写答案
type BuildTuple<T extends number, K extends any[] = []> = K["length"] extends T ? K : BuildTuple<T, [...K, unknown]>
type MinusOne<T extends number> = BuildTuple<T> extends [infer A, ...infer B] ? B["length"] : -1;
type GreaterThan<A extends number, B extends number> = MinusOne<A> extends 0 ? (
MinusOne<B> extends 0 ? false: false
) : (
MinusOne<B> extends 0 ? true: GreaterThan<MinusOne<A>, MinusOne<B>>
)
答案
type GreaterThan<
T extends number,
U extends number,
A extends any[] = []
> = A['length'] extends T
? false
: A['length'] extends U
? true
: GreaterThan<T, U, [...A, 0]>;
答案更加巧妙和简洁哦。
16. Flip
实现一个 just-flip-object 类型
示例:
Flip<{ a: 'x'; b: 'y'; c: 'z' }>; // {x: 'a', y: 'b', z: 'c'}
Flip<{ a: 1; b: 2; c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'}
Flip<{ a: false; b: true }>; // {false: 'a', true: 'b'}
自写答案
type Flip<T extends Record<PropertyKey, string | number | bigint | boolean | null | undefined>> = {
[P in keyof T as `${T[P]}`]: P
}
答案
type Flip<T> = {
[K in keyof T as T[K] extends
| string
| number
| bigint
| boolean
| null
| undefined
? `${T[K]}`
: never]: K;
};
使用 as 则之前的任何内容都只有形式上的作用。
17. Fibonacci
实现一个输入参数为数字 T 的泛型类型 Fibonacci<T>, 返回 T 对应的斐波那契数
斐波那契数列: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
自写答案
type BuildTuple<T extends number, K extends any[] = []> = K["length"] extends T ? K : BuildTuple<T, [...K, unknown]>;
type Add<T extends number, K extends number> = [...BuildTuple<T>, ...BuildTuple<K>]["length"];
type MinusOne<T extends number> = BuildTuple<T> extends [infer A, ...infer B] ? B["length"] : -1;
type Fibonacci<T extends number> = T extends 1 ? 1 : (
T extends 2 ? 1 : (
Add< Fibonacci<MinusOne<T>>, Fibonacci<MinusOne<MinusOne<T>>>>
)
)
答案
type Fibonacci<
T extends number,
ACC extends [any[], any[], any[]] = [[any], [any], [any, any]]
> = T extends 1 | 2
? 1
: ACC[2]['length'] extends T
? ACC[0]['length']
: Fibonacci<T, [[...ACC[0], ...ACC[1]], [...ACC[0]], [any, ...ACC[2]]]>;
结合第 15 题,需要提升对数组的使用技巧。
18. InorderTraversal
实现二叉树的中序遍历
示例:
const tree1 = {
val: 1,
left: null,
right: {
val: 2,
left: {
val: 3,
left: null,
right: null,
},
right: null,
},
} as const;
type A = InorderTraversal<typeof tree1>; // [1, 3, 2]
自写答案
type InorderTraversal<T extends TreeNode, R extends number[] = []> = T["left"] extends TreeNode ? (
T["right"] extends TreeNode ? (
[...InorderTraversal<T["left"], R>, T["val"], ...InorderTraversal<T["right"], R>]
) : [...InorderTraversal<T["left"], R>, T["val"]]
) : (
T["right"] extends TreeNode ? (
[...R, T["val"], ...InorderTraversal<T["right"], R>]
) : [...R, T["val"]]
)
答案
interface TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
}
type InorderTraversal<T extends TreeNode | null> = T extends TreeNode
? [
...(T['left'] extends TreeNode ? InorderTraversal<T['left']> : []),
T['val'],
...(T['right'] extends TreeNode ? InorderTraversal<T['right']> : [])
]
: [];
注意定义 TreeNode 的时候可以立即开始递归。
19. BEM style string
Block,Element,Modifier 方法(BEM)是 CSS 中类的流行命名约定。
例如,块组件用 btn 表示,依赖于块的元素用 btn__price 表示,更改块样式的修饰符将用 btn--big 或者 btn__price--warning表示。
实现从这三个参数生成字符串联合的 BEM<B, E, M>。其中 B 是字符串字面量,E 和 M 是字符串数组(可以为空)。
自写答案
没看懂题目让干啥,直接抄答案好了。
答案
type BEM<
B extends string,
E extends string[],
M extends string[]
> = `${B}${E extends [] ? '' : `__${E[number]}`}${M extends []
? ''
: `--${M[number]}`}`;
这道题大概是想说在 `${}` 中也可以使用三目运算符吧!