14类型体操之基本原理

73 阅读3分钟

一. 体操基本原理(Type Challenge)

  • 最简单的例子
type A = 1;
type B = 1 | 2;
type Result = A extends B ? true : false; // true
  • 两个条件的例子
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
  • 判断是否是空元组类型的例子
type A = [];
type isEmptyArray<Arr extends unknown[]> = Arr['length'] extends 0
  ? true
  : false;

type Result = isEmptyArray<A>; // true
  • 使用infer,判断非空数组的例子
type A = [1];
type NotEmpty<Arr extends unknown[]> = Arr extends [...infer X, infer Last]
  ? // [...infer X, infer Last] 这个可以理解成[...unknown[], unknown],满足这个条件表示至少有一个元素,  infer表示后面跟的变量是类型
    true
  : false;
type Result = NotEmpty<A>;
  • 递归的例子
type A = ['ji', 'ni', 'tai', 'mei'];
type Reverse<Arr extends unknown[]> = Arr extends [...infer Rest, infer Last]
// Rest可以是[]
  ? [Last, ...Reverse<Rest>]
  : Arr;
type Result = Reverse<A>; // ['mei', 'tai', 'ni', 'ji']
  • 模式匹配 + infer"引用"的例子
type Tuple = ['ji', 'ni', 'tai', 'mei'];

type Result1 = Tuple extends [infer First, ...infer Rest] ? First : never; // 'ji'
// 如果没用到也可以写成 Tuple extends [infer First, ...String[]] 
type Result2 = Tuple extends [infer First, ...infer Rest] ? Rest : never; // 'ni', 'tai', 'mei'
// 如果没用到也可以写成 Tuple extends [String, ..infer Rest] 

二. 元组体操

  • 元组扩展的例子
type A = [1]
type B = [...A, 2]
  • 两个元组相加
type B = [1, 2];
type C = [3, 4];
type D = [...B, ...C];
  • 返回元组最后一项
type D = [1, 2, 3, 4];
type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never;
type E = Last<D>;
  • 拿到除了最后一项的其它项
type D = [1, 2, 3, 4];
type NoLast<T> = T extends [...infer X, unknown] ? X : never;
type E = NoLast<D>;

三. 字符串的相关体操

  • 首字母大写例子
type A = 'fang';
type B = Capitalize<A>;
// "Fang"
type C = 'ji' | 'ni' | 'tai' | 'mei';
type X = Capitalize<C>;
// "Ji" | "Ni" | "Tai" | "Mei"
  • 其它的几个内置Uppercase,Lowercase,Uncapitalize

    • Uppercase 全大写
    • Lowercase 全小写
    • Uncapitalize 首字母小写
  • 模板字符串,字符串连接例子

type A = 'ji';
type B = 'ni';
type C = 'tai';
type D = 'mei';
type X = `${A} ${B} ${C} ${D}`;
// "ji ni tai mei"
  • 获取字符串第一个自符的例子
type A = 'ji ni tai mei';
type First<T extends string> = T extends `${infer F}${string}` ? F : never;
type Result = First<A>; // j
  • 归获取字符串最后一个字符,通过将字符串转成远组来做
type A = 'ji ni tai mei';

type LastOfTuple<T extends unknown[]> = T extends [...infer _, infer L]
  ? L
  : never;

type StringToTuple<S extends string> = S extends `${infer F}${infer R}`
  ? [F, ...StringToTuple<R>]
  : [];

type LastOfString<S extends string> = LastOfTuple<StringToTuple<S>>;

type R = LastOfString<'ji ni tai mei x'>;

四. infer的文档

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
type X = Flatten<Array<number>>; // number

五. ts中的递归层数限制

  • 根据测试,最多递归48
type A = [
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
  'ji', 'ni', 'tai', 'mei',
];
// 再多加一项就会报错
type Reverse<Arr extends unknown[]> = Arr extends [...infer Rest, infer Last]
  ? // Rest可以是[]
    [Last, ...Reverse<Rest>]
  : Arr;
type Result = Reverse<A>; // ['mei', 'tai', 'ni', 'ji']
  • readonly的对象层数最多一层
    • 但是可以自己去写一个递归的readonly
    • 这里估计也是48层
interface SomeObject {
  a: {
    b: {
      c: number;
    };
  };
}

const obj: Readonly<SomeObject> = {
  a: {
    b: {
      c: 1,
    },
  },
};

obj.a.b.c = 2; // Readonly只有一层,这样不报错

type DeepReadonly<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
};

const obj2: DeepReadonly<SomeObject> = {
  a: {
    b: {
      c: 1,
    },
  },
};

obj2.a.b.c = 3; // 这样会报错

六. 数组和联合类型之间的转换

  • 也是用递归去做,参考之前提到的stringToTuple的写法
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}`
  ? First | StringToUnion<Rest>
  : never;

type Result = StringToUnion<'jinitaimei'>;