浅尝类型体操

基本原理

if(A <= B)

if(A <= B) true else false

1
2
3
type A = 1;
type B = 1 | 2;
type Result = A extends B ? true : false; // true

if(A <= B) and (C <= D)

1
2
3
4
5
6
7
8
9
10
11
type A = 1;
type B = 1 | 2;
type C = 3;
type D = 3 | 4;
type Result = A extends B
? C extends D
? 'true, true'
: 'true, false'
: C extends D
? 'false, true'
: 'false, false'; // "true, true"

空元组

1
2
3
4
5
type A = [];
type IsEmptyArray<Arr extends unknown[]> = Arr['length'] extends 0
? true
: false;
type Result = IsEmptyArray<A>; // true

元组与数组的区别是:数组长度没有限制,而元组长度有限制;

可以理解为:元组就是长度固定的数组

非空元组

1
2
3
4
5
type A = [1];
type NotEmpty<Arr extends unknown[]> = Arr extends [...infer X, infer Last]
? true
: false;
type Result = NotEmpty<A>; // true

[...infer X, infer Last] 表示至少存在一个元素的数组,那么 Arr extends [...infer X, infer Last] 表示的意思是:Arr 是否属于(包含于)一个非空数组

判断是否为非空元组的两种方法:

  1. array['length'] !== 0
  2. array extends [...infer X, infer Rest] === true

递归

1
2
3
4
5
type A = ['Ich', '☆', 'liebe', '☆', 'dich'];
type Reverse<Arr extends unknown[]> = Arr extends [...infer Rest, infer Last]
? [Last, ...Reverse<Rest>]
: Arr;
type Result = Reverse<A>; // ["dich", "☆", "liebe", "☆", "Ich"]

注意:递归是有层数限制的

模式匹配 + infer 引用

1
2
3
type Tuple = ['Ich', '☆', 'liebe', '☆', 'dich'];
type Result1 = Tuple extends [infer First, ...infer Rest] ? First : never; // "Ich"
type Result2 = Tuple extends [infer First, ...infer Rest] ? Rest : never; // ["☆", "liebe", "☆", "dich"]
  • inter 出现在条件类型中,通常与 extends 关键字一起使用
  • infer 显示地声明了一个新的泛型变量,用于表示待推断的类型
  • 当一个类型无法通过简单的方式访问到时, 我们可以使用 infer 关键字来实现对其的快速访问;换言之,使用 infer 往往是因为后面需要用到对应的变量,比如上述的 FirstRest,表示做一个引用(个人理解)

不写引用,直接写类型也是可以的

1
2
3
type Tuple = ['Ich', '☆', 'liebe', '☆', 'dich'];
type Result1 = Tuple extends [infer First, ...string[]] ? First : never; // "Ich"
type Result2 = Tuple extends ['Ich', ...infer Rest] ? Rest : never; // ["☆", "liebe", "☆", "dich"]

元组体操

[1] => [1, 2]

1
2
type A = [1];
type B = [...A, 2]; // [1, 2]

[1, 2] [3, 4] => [1, 2, 3, 4]

1
2
3
type B = [1, 2];
type C = [3, 4];
type D = [...B, ...C]; // [1, 2, 3, 4]

[1, 2, 3, 4] => 4

1
2
3
4
5
type D = [1, 2, 3, 4];
// 注意 1,2,3,4 都是类型,不是值
type Last<T> = T extends [...items: unknown[], last: infer X] ? X : never;
type E = Last<D>;
//   ^--- E = 4 注意这里的 4 是类型,不是值

这里顺便提一下错误的写法

1
2
3
4
5
type D = [1, 2, 3, 4];
// 注意 1,2,3,4 都是类型,不是值
type Last<T extends unknown[]> = T[T["length"] - 1]
// TS 并没有提供 - 1 操作
type E = Last<D>

[1, 2, 3, 4] => [1, 2, 3]

1
2
3
4
5
type D = [1, 2, 3, 4];
// 注意 1,2,3,4 都是类型,不是值
type NoLast<T> = T extends [...infer X, unknown] ? X : never;
type E = NoLast<D>;
//   ^--- E = [1,2,3] 注意是类型,不是值

字符串体操

‘a’ => ‘A’

1
2
3
4
5
6
type A = 'vivy';
type B = Capitalize<A>;
//   ^-- type B = "Vivy"
type C = 'Ich' | '☆' | 'liebe' | '☆' | 'dich';
type X = Capitalize<C>;
//   ^-- type X = "Ich" | "☆" | "Liebe" | "Dich"

对联合类型使用 Capitalize 依旧遵循前面讲的分配率,即对每个类型单独进行大写操作后再合并起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 全变成大写
 */
type Uppercase<S extends string> = intrinsic;
/**
 * 全变成小写
 */
type Lowercase<S extends string> = intrinsic;
/**
 * 首字母大写
 */
type Capitalize<S extends string> = intrinsic;
/**
 *首字母小写
 */
type Uncapitalize<S extends string> = intrinsic;

intrinsic 表示是 TS 内置的泛型

‘a’ ‘b’ => ‘ab’

1
2
3
4
5
6
7
type A = 'Ich';
type B = '☆';
type C = 'liebe';
type D = '☆';
type E = 'dich';
type X = `${A} ${B} ${C} ${D} ${E}`;
//   ^-- type X = "Ich ☆ liebe ☆ dich"

‘ab’ => ‘a’

1
2
3
4
type A = "Ich ☆ liebe ☆ dich"
type First<T> = T extends `${infer F}${string}` ? F : never
type Result = First<A>
//   ^-- "I"

‘ab’ => ‘b’

字符串模式匹配的缺陷:只能获取到字符串中的第一个字符,获取不到最后一个字符

那么如何才能做到获取最后一个字符呢?

这里给出思路:

  1. 我们可以获取元组的最后一项
  2. 字符串可以转为元组
  3. 我们可以获取字符串的最后一项
1
2
3
4
5
6
7
8
9
10
type LastOfTuple<T extends unknown[]> = T extends [...infer _, infer Last]
? Last
: never;
type StringToTuple<S extends string> = S extends `${infer First}${infer Rest}`
? [First, ...StringToTuple<Rest>]
: [];
type LastOfString<T extends string> = LastOfTuple<StringToTuple<T>>;
type Result = LastOfString<'Ich ☆ liebe ☆ dich'>;
// ^-- "h"

string => 联合类型

1
2
3
4
5
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}`
? First | StringToUnion<Rest>
: never;
type Result = StringToUnion<'Ich☆liebe☆dich'>;
// type Result = "I" | "c" | "h" | "☆" | "l" | "i" | "e" | "b" | "d"

注意:联合类型自动去重了

string => 元组

1
2
3
4
5
type StringToTuple<S extends string> = S extends `${infer First}${infer Rest}`
? [First, ...StringToTuple<Rest>]
: [];
type Result = StringToTuple<'Ich☆liebe☆dich'>;
// type Result = ["I", "c", "h", "☆", "l", "i", "e", "b", "e", "☆", "d", "i", "c", "h"]

注意:元组没有去重


(●'◡'●)ノ♥