爆刷100道typescript类型体操,我明白了!(附百道题解)

118 阅读5分钟

一、基础学习篇

开始做类型体操前,需要学习的基本操作和常用实现模板

关键字

keyof

keyof 后跟一个对象,取所有的key组成联合类型

// 作用在对象上
type O = { foo: string, bar: string };
type OKeys = keyof O;
// "foo" | "bar"

in

  1. in 后面必须跟联合类型(Union Type),遍历联合类型的每个成员
type Keys = "foo" | "bar";
type Obj = {
  [K in Keys]: any;
};
// { foo: any; bar: any; }
  1. 通常和 keyof 配合使用
// 下面结果相同
type Person = { name: string; age: number };

type Copy = {
  [K in keyof Person]: Person[K];
};
  1. keyof + in + as 可以对键名映射或过滤
type Person = { name: string; age: number; hidden: boolean };

// 过滤掉 hidden
type VisiblePerson = {
  [K in keyof Person as Exclude<K, "hidden">]: Person[K];
};

extends

  1. 在泛型参数中使用,约束参数的范围
type Include<T extends unknown[]> = any;
  1. 最常用的方式! 作为条件类型判断
type IsString<T> = T extends string ? true : false;
  1. 分布式计算:当 extends 左边是联合类型时
type ToArray = T extends any ? T[] : never;
type R = ToArray<string | number>;
// string[] | number[]

// 注意:[T] extends [U] 可以阻止分布式计算

infer

在条件类型 A extends B ? X : Y 中,”提取” 或 ”推断” 类型,可以理解为声明一个 “占位” 变量

  1. 推断
type ReturnType<T extends (...args: any) => any> =
	T extends (...args: any) => infer R 
		? R 
		: any;
  1. 提取
type StringFirst<T extends string> =
	T extends `${infer First}${string}`
		? First
		: never;

typeof

typeof + 运行时值 提取值对应的类型

const arr = ['a', 'b', 'c'] as const;
type A = typeof arr;
// readonly ["a", "b", "c"]

never

一般表示 extends 中不可达的分支

type First<T extends any[]> = T extends [] ? never : T[0];

工具/板子/基本操作

元组转联合类型

const t = ['a', 'b'] as const;
// 'a' | 'b'
type T = (typeof t)[number];

严格的 Equals

// 便捷的 Equal
type Equals<T, U> = T extends U ? U extends T ? true : false : false;

// 严格的 Equal
// https://github.com/type-challenges/type-challenges/discussions/9100#discussioncomment-6896958
type StrictEquals<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;
  

字符串相关

迭代处理字符串

type HandleString<S extends string> = 
	S extends `${infer First}${infer Tail}` 
		? HandleString<Tail> 
		: S;

字符串转元组

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

数组相关

Includes 是否包含某Type

type Includes<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, U> extends true
      ? true
      : Includes<Rest, U>
    : false;

ReplaceAt 替换指定索引的Type

type ReplaceAt<
  T extends readonly unknown[],
  Index extends number,
  U
> =
  Index extends keyof T
    ? {
        [K in keyof T]: K extends `${Index}` ? U : T[K];
      }
    : T;

Concat 合并数组

type Concat<
	T extends readonly unknown[],
	U extends readonly unknown[]
> = [...T, ...U];

对象相关

移除只读属性 -readonly

type Mutable<T extends object> = {
  -readonly [K in keyof T]: T[K];
}

合并交叉类型

type MergeIntersection<T> = { [K in keyof T]: T[K] };

函数

⚠️ 函数也是对象,注意判断

// true
type Flag = Function extends Record<string, any> ? true : false;

二、百道题解篇

题目来源:github.com/type-challe…

⬇️第一阶段:难度 Easy⬇️

13-Hello World

  1. 热身 🤸
type HelloWorld = string;

4-Pick

  1. extends 限制入参 K 仅为 T 的属性
  2. in 遍历联合类型
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

7-Readonly 对象属性只读

  1. 遍历属性,全部添加属性修饰符
type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

11-元组转换为对象

  1. 元组转为k-v相同的对象
  2. 使用内置类型 PropertyKey 限制元组项的类型
type TupleToObject<T extends readonly PropertyKey[]> = {
  [P in T[number]]: P;
};

14-第一个元素

两种解法

  1. 下标取元素
  2. infer 取元素
type First<T extends any[]> = T extends [] ? never : T[0];

type First<T extends any[]> =  T extends [infer U, ...infer reset] ? U : never;

18-获取元组长度

  1. 泛型约束只接受元组 readonly unknown[]
type Length<T extends readonly unknown[]> = T['length'];

43-实现 Exclude

  1. 这里 T、U 都是联合类型
  2. extends + 左侧联合类型,会触发分布式计算
type MyExclude<T, U> = T extends U ? never : T;

// 例如:
// T = 'a'|'b'|'c'|'d'
// U = 'c'|'d'

// 触发分布式计算
// 'a' extends 'c'|'d' → false → 结果 'a'
// 'b' extends 'c'|'d' → false → 结果 'b'
// 'c' extends 'c'|'d' → true  → 结果 never
// 'd' extends 'c'|'d' → true  → 结果 never

// 最终结果 'a'|'b'

189-Awaited

  1. 使用内置PromiseLike + infer 占位,获取结果
  2. 如果结果还是一个 Promise,需要等它完成,递归处理
type MyAwaited<T> =
  T extends PromiseLike<infer U>
    ? U extends PromiseLike<any>
      ? MyAwaited<U>
      : U
    : never;

268-If

  1. C为真返回 T,C为假返回F
  2. 体会 extends 条件使用
type If<C extends boolean, T, F> = C extends true ? T : F;

533-Concat

合并两个数组

  1. 声明元组 测试用例要求
  2. 使用 ... 展开运算符合并数组
type Tuple = readonly unknown[];
type Concat<T extends Tuple, U extends Tuple> = [...T, ...U];

898-Includes

  1. 了解严格相等的 Hack 写法
  2. 递归判断每一项 equals
type Includes<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, U> extends true
      ? true
      : Includes<Rest, U>
    : false;

3057-Push

  1. ... 展开之前数组
  2. 新增数组项,合并为新数组
type Push<T extends unknown[], U> = [...T, U];

3060-Unshift

同上

3312-Parameters

type MyParameters<T extends (...args: any[]) => any> =
	T extends (...args: infer U[]) => any
		? [...U]
		: never;

⬇️ 第二阶段 难度 Middle ⬇️

2-ReturnType 获取函数返回类型

  1. infer 占位提取返回类型
type MyReturnType<T extends (...args: any[]) => any>
  = T extends (...args: any) => infer R
    ? R
    : never;

3-实现 Omit

两种解法

  1. 方式依赖 Pick + Exclude 剔除联合类型中的值
  2. as K extends 对遍历的属性值进行过滤
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

type MyOmit<T, U extends keyof T> = {
  [P in keyof T as P extends U ? never : P]: T[P];
};

8-对象部分属性只读

利用已有的类型实现

  1. Omit 剔除不要只读的属性
  2. Pick 选出需要只读的属性
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & Readonly<Pick<T, K>>;

9-对象属性只读(递归)

🍀 新的技巧

  1. 这里 keyof 后跟对象类型才生效,如果没有可枚举的键,返回 never
  2. 所以 keyof T[P] extends never 代表不是一个对象
type DeepReadonly<T> = {
  readonly [P in keyof T]: keyof T[P] extends never ? T[P] : DeepReadonly<T[P]>;
};

10-元素转合集(联合类型)

  1. Tuple[number] 返回元组项的联合类型
type TupleToUnion<T extends readonly unknown[]> = T[number];

12-可串联构造器

type Chainable<R = object> = {
  option<K extends string, V>(
    key: Exclude<K, keyof R>,
    value: V
  ): Chainable<Omit<R, K> & Record<K, V>>;

  get(): R;
}

15-最后一个元素

了解解构数组 + infer

type Last<T extends any[]> = T extends [...infer _, infer U] ? U : never;

16-排除最后一项 Pop

了解解构数组 + infer

type Pop<T extends any[]> = T extends [...infer Rest, unknown] ? Rest : T;

20-Promise.all

  1. keyof + 元组 返回元组的下标 0,1,2,3…
  2. 结合之前的 Awaited 获取每项 Promise 的返回值
declare function PromiseAll<T extends unknown[]>(values: readonly [...T]): Promise<{
  [K in keyof T]: Awaited<T[K]>;
}>;

62-查找类型 LookUp

  1. 利用 extends + 左侧联合类型,分布式计算
type LookUp<U, T extends string> = U extends { type: T } ? U : never;

106-去除左侧空白

  1. 声明空白联合类型
  2. infer U 代表未处理的 Rest 字符串,递归计算去除
type Space = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Space}${infer U}` ? TrimLeft<U> : S;

108-去除两端空白字符 Trim

同上,写一个 TrimRight 结合起来

type Space = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Space}${infer Rest}` ? TrimLeft<Rest> : S;
type TrimRight<S extends string> = S extends `${infer Rest}${Space}` ? TrimRight<Rest> : S;

type Trim<S extends string> = TrimRight<TrimLeft<S>>;

110-Capitalize

首字母大写,两种思路

  1. 使用 Uppercase
  2. 写一个字母表代替 Uppercase
type MyCapitalize<S extends string> = S extends `${infer X}${infer Tail}` ? `${Uppercase<X>}${Tail}` : S;

116-Replace

模式匹配

type Replace<S extends string, From extends string, To extends string> =
  From extends ''
  ? S
  : S extends `${infer L}${From}${infer R}`
    ? `${L}${To}${R}`
    : S;

119-ReplaceAll

模式匹配 + 递归

type ReplaceAll<S extends string, From extends string, To extends string> =
  From extends ''
  ? S
  : S extends `${infer L}${From}${infer R}`
    ? `${L}${To}${ReplaceAll<R, From, To>}`
    : S;

191-追加参数

了解参数类型声明的写法

type AppendArgument<Fn extends (...args: any) => any, T> = 
  (...args: [...Parameters<Fn>, T]) => ReturnType<Fn>;

298-Length of String

  1. 字符串遍历转数组
  2. 返回数组[”length”]
type StringToArray<S extends string, Arr extends string[] = []> = 
  S extends `${infer First}${infer Tail}`
  ? StringToArray<Tail, [...Arr, First]>
  : Arr;

type LengthOfString<S extends string> = StringToArray<S>['length'];

459-Flatten

  1. 递归数组每一项
  2. 判断是否是数组,是则继续递归,否则递归剩余部分
type Flatten<T extends any[]> =
  T extends [infer First, ...infer Tail]
  ? First extends unknown[]
    ? [...Flatten<First>, ...Flatten<Tail>]
    : [First, ...Flatten<Tail>]
  : T;

527-Append to object

  1. 旧key用旧值,新key用新值
type AppendToObject<T, K extends PropertyKey, V> = {
  [P in keyof T | K]: P extends keyof T ? T[P] : V;
};

529-Absolute

返回一个正数字符串

  1. “-” 开头,返回剩余部分
type Absolute<T extends number | string | bigint> =
  `${T}` extends `${infer First}${infer Tail}`
	  ? First extends '-'
	    ? `${Tail}`
	    : `${T}`
	  : T;

531-String to Union

两种方式

  1. 递归构造联合类型
  2. 转数组,返回数组[number]
type StringToUnion<T extends string> =
  T extends `${infer First}${infer Tail}`
  ? First | StringToUnion<Tail>
  : never;

599-Merge

两种方式

  1. 重新合并交叉类型
  2. 手动构造对象,优先取 S[K]
type Merge<F, S, P = Omit<F, keyof S> & S> = {
  [K in keyof P]: P[K];
};

type Merge<F, S> = {
  [K in keyof (F & S)]: K extends keyof S ? S[K] : K extends keyof F ? F[K] : never;
};

612-KebabCase

驼峰字符串转连字符

  1. 拆分单个字符,递归处理
  2. 头部额外的条件分支,直接变小写,非头部变 “-小写”
  3. 没有使用内置 Uppercase 因为 F extends Uppercase<F> 非字母时始终为 True
type UppercaseAlphabet = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';

type KebabCase<S extends string, IsHead = true> =
  S extends `${infer F}${infer Tail}`
    ? F extends UppercaseAlphabet
      ? IsHead extends true
        ? `${Lowercase<F>}${KebabCase<Tail, false>}`
        : `-${Lowercase<F>}${KebabCase<Tail, false>}`
      : `${F}${KebabCase<Tail, false>}`
    : S;

645-Diff

获取两个接口类型的属性差值

  1. 互相剔除 keyof
  2. 联合起来
  3. 合并交叉类型,返回结果
type MergeIntersection<T> = { [K in keyof T]: T[K] };

type Diff<A, B> = MergeIntersection<Omit<A, keyof B> & Omit<B, keyof A>>;

949-AnyOf

数组中有任一项为真,返回 true

  1. 出现了分布式计算
  2. 分布式计算每项都为假,返回 false
type Falsy =
  | null
  | undefined
  | false
  | 0
  | ''
  | []
  | Record<string, never>

type AnyOf<T extends readonly unknown[]> = T[number] extends Falsy ? false : true;

1042-IsNever

  1. 使用 [T] extends [never] 避免分布式计算
type IsNever<T> = [T] extends [never] ? true : false;

1130-ReplaceKeys

  1. U 是接口联合类型、T 是字符串联合类型、Y 是要替换的新接口
  2. 遍历 keyof U,如果属性是T和Y的key则替换,否则使用原来的值
type ReplaceKeys<U, T extends string, Y> = {
  [K in keyof U]: 
    K extends T
      ? K extends keyof Y
        ? Y[K]
        : never
      : U[K]
};

1367-Remove Index Signature

移除掉形如 [k: string],仅保留字面量的 Key

  1. 仅保留字面量的key,通用类型的key一律返回 never
  2. 利用 as 过滤 key
type RemoveIndexSignature<T> = {
  [K in keyof T as
    number extends K
      ? never
      : string extends K 
        ? never 
        : symbol extends K 
          ? never 
          : K
  ]: T[K];
};

1978-Percentage Parser

拆分一个百分数字符串,如 -80% => ["-", "80", "%"]

type ParseSign<T> = T extends `${infer X extends '+'|'-'}${string}` ? X : '';
type ParseNumber<T> = T extends `${ParseSign<T>}${infer X}${ParsePercent<T>}` ? X : '';
type ParsePercent<T> = T extends `${string}${infer X extends '%'}` ? X : '';

type PercentageParser<T extends string> = [ParseSign<T>, ParseNumber<T>, ParsePercent<T>]

2070-Drop Char

从字符串中剔除指定字符

  1. 逐个拆分字符,和 C 比较
  2. 递归拼接
type DropChar<S extends string, C extends string> = 
  S extends `${infer First}${infer Tail}`
    ? `${First extends C ? '' : First}${DropChar<Tail, C>}`
    : '';

2595-PickByType

Pick 指定 Type 的 key

  1. 利用 as 过滤属性的类型
type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: U;
};

2688-StartsWith

Easy!

type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false;

2688-EndsWith

Easy!

type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false;

2757-PartialByKeys

指定联合类型 keys 可选 ?

  1. 属于 K 的,可选
  2. 之前的 keyof 剔除 K 后,不可选
type Merge<T> = { [K in keyof T]: T[K] };
type PartialByKeys<T, K extends keyof T = any> =
  Merge<
    {
      [P in keyof T as P extends K ? P : never]?: T[P];
    } & {
      [P in Exclude<keyof T, K>]: T[P];
    }
  >;

2759-RequiredByKeys

指定联合类型key,必选


type Merge<T> = { [K in keyof T]: T[K] };
type RequiredByKeys<T, K extends keyof T = keyof T> = Merge<T & Required<Pick<T, K>>>;

2793-Mutable

全部变为可变,去除 readonly

  1. 使用 -readonly 可以去除
type Mutable<T extends Record<any, any>> = {
  -readonly [K in keyof T]: T[K];
};

2852-OmitByType

2595-PickByType 的反例

type OmitByType<T, U> = {
  [K in keyof T as T[K] extends U ? never : K]: T[K];
};

2946-ObjectEntries

返回类似 Object.entries

  1. 分布式计算,遍历联合类型 K = keyof T
  2. 返回具体的类型值要注意:undefined 返回、可选的返回变为必选
type ObjectEntries<T, K extends keyof T = keyof T> =
  K extends PropertyKey
    ? [K, T[K] extends undefined ? undefined : Required<T>[K]]
    : never;

3062-Shift

Easy!

type Shift<T extends unknown[]> = T extends [unknown, ...infer Rest] ? Rest : T;

3188-Tuple to Nested Object

示例:TupleToNestedObject<['a', 'b'], number> // { a: { b: number }}

  1. 递归处理对象就好
  2. 注意要写 [K in First]
type TupleToNestedObject<T extends unknown[], U> =
  T extends [infer First extends PropertyKey, ...infer Rest]
    ? { [K in First]: TupleToNestedObject<Rest, U> }
    : U;

3192-Reverse

  1. 不断拆最后一项,放到第一项
  2. 递归处理反转
type Reverse<T extends unknown[]> =
  T extends [...infer Rest, infer U]
    ? [U, ...Reverse<Rest>]
    : T;

3196-Flip Arguments

反转函数参数的类型声明

两种解法

// 1. 利用内置对象
type FlipArguments2<Fn extends (...args: any) => any> = (...args: Reverse<Parameters<Fn>>) => ReturnType<Fn>;

// 2. 手动 infer + Reverse 翻转
type FlipArguments<Fn extends (...args: any) => any> =
  Fn extends (...args: infer A) => infer R
    ? (...args: Reverse<A>) => R
    : never;

3243-FlattenDepth

指定深度数组扁平化

type FlattenDepth<
  T extends unknown[],
  Depth extends number = 1,
  Count extends unknown[] = [],
> = 
  Count['length'] extends Depth
    ? T
    : T extends [infer F, ...infer Rest]
      // 处理一项,是数组则递归
      ? F extends unknown[]
        // 处理一项,层次+1
        ? [...FlattenDepth<F, Depth, [...Count, unknown]>, ...FlattenDepth<Rest, Depth, Count>]
        : [F, ...FlattenDepth<Rest, Depth, Count>]
      : T;

3326-BEM style string

type BEM<B extends string, E extends string[],M extends string[]> =
  `${B}${E extends [] ? '' : `__${E[number]}`}${M extends [] ? '' : `--${M[number]}`}`;

3376-InorderTraversal

中序遍历,左根右

interface TreeNode {
  val: number
  left: TreeNode | null
  right: TreeNode | null
}

type InorderTraversal<T extends TreeNode | null> =
  T extends TreeNode
    ? [...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']>]
    : [];

4179-Flip

接口 K-V 转 V-K

type Flip<T extends  Record<any, any>> = {
  [K in keyof T as `${T[K]}`]: K;
};

4425-Greater Than

比较两个数的大小(仅解决了非大数的场景)

  1. 转换为比较元组A和元组B
  2. 每次遍历砍掉一项,谁先为空谁就短
type BuildTuple<N extends number, T extends unknown[] = []> = 
  T['length'] extends N
    ? T
    : BuildTuple<N, [...T, unknown]>;

type GreaterTupleLengthThan<A extends unknown[], B extends unknown[]> = 
  A extends [infer _, ...infer RestA]
    ? B extends [infer _, ...infer RestB]
      ? GreaterTupleLengthThan<RestA, RestB>
      : A
    : B;

type GreaterThan<A extends number, B extends number> = GreaterTupleLengthThan<BuildTuple<A>, BuildTuple<B>>;

4471-Zip

接受两个元组,返回 [[A[0], B[0]], [A[1], B[1]]]

  1. 两个元组每一项都拆分处理
  2. 递归
type Zip<A extends unknown[], B extends unknown[]> =
  A extends [infer A0, ...infer TRest]
    ? B extends [infer B0, ...infer URest]
      ? [[A0, B0], ...Zip<TRest, URest>]
      : []
    : [];

4484-IsTuple

判断是元组,而不是数组或never

  1. 要避免T是联合类型,阻止分布式计算
  2. ‼️元组的 length 属性是字面量,不是 number 类型
type IsTuple<T> = 
  [T] extends [never]
    ? false
    : T extends readonly any[]
      ? number extends T['length']
        ? false
        : true
      : false;

4499-Chunk

n 个一组拆分数组

  1. 声明临时变量存每一段chunk、声明变量R存最后结果
  2. 递归遍历,直到 T 每一项都处理完成
type Chunk<
  T extends unknown[],
  N extends number = 1,
  Temp extends unknown[] = [],
  Result extends unknown[] = [],
> =
  T extends [infer Head, ...infer Tail]
    ? Temp['length'] extends N
      ? Chunk<Tail, N, [Head], [...Result, Temp]>
      : Chunk<Tail, N, [...Temp, Head], Result>
    : Temp['length'] extends 0
      ? Result
      : [...Result, [...Temp]];

4518-Fill

数组指定范围内,全部填充指定值

type Fill<
  T extends unknown[],
  NewType,
  Start extends number = 0,
  End extends number = T['length'],
  Count extends unknown[] = [],
  Flag extends boolean = Count['length'] extends Start ? true : false,
> =
  Count['length'] extends End
    ? T
    // 一定在 End 之前
    : T extends [infer First, ...infer Rest]
      ? Flag extends false
        // 没到 Start,用 Flag 默认值判断是否到了
        ? [First, ...Fill<Rest, NewType, Start, End, [...Count, unknown]>]
        // 刚好到 Start,Flag 要保持 True
        : [NewType, ...Fill<Rest, NewType, Start, End, [...Count, unknown], true>]
      : T;

4803-Trim Right

同 TrimLeft

type Space = ' ' | '\n' | '\t';
type TrimRight<S extends string> = S extends `${infer Rest}${Space}` ? TrimRight<Rest>: S;

5117-去除数组指定元素

U 是一个数组,转联合类型用于判断

type ToUnion<T> = T extends unknown[] ? T[number] : T;

type Without<T extends unknown[], U> = 
  T extends [infer F, ...infer Rest]
    ? F extends ToUnion<U>
      ? [...Without<Rest, U>]
      : [F, ...Without<Rest, U>]
    : T;

5140-Trunc

数字截尾取整

type Trunc<S extends number | string> =
  `${S}` extends `${infer L}.${string}`
    ? L extends '' | '-' | '+'
      ? `${L}0`
      : L
    : `${S}`;

5153-IndexOf

返回指定值所在数组的索引

  1. 利用 Count[’length’] 计数
  2. 注意严格 Equals
type IndexOf<T extends unknown[], U, Count extends unknown[] = []> =
  T extends [infer F, ...infer Rest]
    ? StrictEqual<F, U> extends true
      ? Count['length']
      : IndexOf<Rest, U, [...Count, unknown]>
    : -1;

5310-Join

数组转字符串,按照 ”,” 分隔

type Join<T extends string[], U extends string | number = ','> =
  T extends [infer F extends string, ...infer Rest extends string[]]
	  // 最后一项不需要 , 结尾
    ? Rest['length'] extends 0
      ? F
      : `${F}${U}${Join<Rest, U>}`
    : '';

5317-LastIndexOf

同 IndexOf,返回最后一个出现的

type LastIndexOf<
	T extends unknown[],
	U,
	Count extends unknown[] = [],
	LatestIndex extends number = -1
> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, U> extends true
	    // 仅相等时,更新最新的index
      ? LastIndexOf<Rest, U, [...Count, unknown], Count['length']>
      : LastIndexOf<Rest, U, [...Count, unknown], LatestIndex>
    : LatestIndex;

5360-Unique

数组去重

type Includes<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, U> extends true
      ? true
      : Includes<Rest, U>
    : false;

type Unique<T extends unknown[], R extends unknown[] = []> =
  T extends [infer F, ...infer Rest]
    ? Includes<R, F> extends true
      ? Unique<Rest, R>
      : Unique<Rest, [...R, F]>
    : R;

5821-MapTypes

示例

type StringToNumber = { mapFrom: string; mapTo: number;}
MapTypes<{foo: string}, StringToNumber> // { foo: number; }
type MapTypes<T extends object, U extends { mapFrom: any; mapTo: any }> = {
  [K in keyof T]:
    // 值类型匹配 mapForm
    T[K] extends U['mapFrom']
      // 这里 U 用例可能是个联合类型,需要匹配联合中具体的一项
      ? U extends { mapFrom: T[K] }
        ? U['mapTo']
        : never
      : T[K];
};

7544-Construct Tuple

构造一个指定长度的元组

type ConstructTuple<L extends number, R extends unknown[] = []> =
  R['length'] extends L
    ? R
    : ConstructTuple<L, [...R, unknown]>;

8640-Number Range

2,6 返回 [2, 3, 4, 5, 6] 数组

type NumberRange<
  L extends number,
  H extends number,
  Count extends unknown[] = [],
  R extends number[] = [],
  Flag extends boolean = Count['length'] extends L ? true : false,
> = 
  Count['length'] extends H
    ? R[number] | H
    : Flag extends true
      ? NumberRange<L, H, [...Count, unknown], [...R, Count['length']], true>
      : NumberRange<L, H, [...Count, unknown], R>
  ;

9142-CheckRepeatedChars

检查数组是否有重复字符

  1. 逐个拆分遍历字符
  2. 判断当前字符有没有在后面出现过 Tail extends string{string}{Head}${string}``
  3. 递归判断
type CheckRepeatedChars<T extends string> =
  T extends `${infer F}${infer Tail}`
    ? Tail extends `${string}${F}${string}`
      ? true
      : CheckRepeatedChars<Tail>
    : false;

9286-FirstUniqueCharIndex

查找第一个唯一字符的索引

type Includes<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? Equals<F, U> extends true
      ? true
      : Includes<Rest, U>
    : false;

type FirstUniqueCharIndex<
  T extends string,
  Count extends unknown[] = [],
  Prev extends unknown[] = [],
> =
  T extends `${infer Head}${infer Tail}`
   ? Includes<Prev, Head> extends true
      // 1. 看当前字符前面有没有出现过
      ? FirstUniqueCharIndex<Tail, [...Count, unknown], [...Prev, Head]>
      // 2. 看后面有没有出现过
      : Tail extends `${string}${Head}${string}`
        ? FirstUniqueCharIndex<Tail, [...Count, unknown], [...Prev, Head]>
        : Count['length']
    : -1;

9616-Parse URL Params

解析 URL 中的 :params

type ParseUrlParams<T> =
  // 砍掉 ":" 及之前的
  T extends `${string}:${infer R}`
    // 砍掉 "/" 之后的,就是参数
    ? R extends `${infer Head}/${infer Tail}`
      // 递归联合
      ? Head | ParseUrlParams<Tail>
      : R
    : never;

9896-获取数组的中间元素

  1. 不断砍两边的项
  2. T['length'] extends 0 | 1 | 2 时,返回结果
type GetMiddleElement<T extends unknown[]> =
  T['length'] extends 0 | 1 | 2
    ? T
    : T extends [unknown, ...infer Rest, unknown]
      ? GetMiddleElement<Rest>
      : never;

9898-找出目标数组中只出现过一次的元素

思路类似FirstUniqueCharIndex,只不过要完全递归完,结果收集起来

type Includes<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, U> extends true
      ? true
      : Includes<Rest, U>
    : false;

type FindEles<T extends unknown[], Prev extends unknown[] = [], R extends unknown[] = []> =
  T extends [infer F, ...infer Rest]
    ? Includes<Prev, F> extends false
      ? Includes<Rest, F> extends false
        ? FindEles<Rest, [...Prev, F], [...R, F]>
        : FindEles<Rest, [...Prev, F], R>
      : FindEles<Rest, [...Prev, F], R>
    : R;

9989-统计数组中的元素个数

  1. 数组扁平化
  2. 写一个单一获取个数的函数
  3. 组合起来问题的拆解
type Flatten<T extends unknown[], R extends unknown[] = []> =
  T extends [infer First, ...infer Tail]
    ? First extends unknown[]
      ? Flatten<[...First, ...Tail], R>
      : Flatten<Tail, [...R, First]>
    : R;

type GetCount<T extends unknown[], E, Count extends unknown[] = []> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, E> extends true
      ? GetCount<Rest, E, [...Count, unknown]>
      : GetCount<Rest, E, Count>
    : Count['length'];

/**
 * 1. 扁平化 flatten
 * 2. 筛选合法的key extends PropertyKey
 * 3. 每个 key 统计个数
 */
type CountElementNumberToObject<
  T extends any[],
  U extends any[] = Flatten<T>
> = {
  [K in U[number]]: GetCount<U, K>;
};

10969-Integer

判断是一个整数

  1. 是一个字面量类型 number extends T 判断排除
  2. 通过 “.” 分隔,判断点后是否都为0
type IsZero<T extends string> =
  T extends `${infer F}${infer Rest}`
    ? F extends '0'
      ? IsZero<Rest>
      : false
    : never;

type Integer<T extends number> = 
  number extends T
  ? never
  : `${T}` extends `${infer _}.${infer Right}`
    ? Right extends string
      ? IsZero<Right> extends true
        ? T
        : never
      : T
    : T;

16259-将类型为字面类型(标签类型)的属性,转换为基本类型

type ToPrimitive<T> =
  T extends object
    ? T extends Function
      ? Function
      : { [K in keyof T]: ToPrimitive<T[K]> }
    // 原始类型或包装类型
    : T extends { valueOf: () => infer U } ? U : T;

17973-DeepMutable

  1. 去除 -readonly
  2. 函数或者非对象,递归处理
type DeepMutable<T extends Record<string, any>> = {
  -readonly [K in keyof T]: 
    T[K] extends Record<string, any>
      ? T[K] extends Function
        ? T[K]
        : DeepMutable<T[K]>
      : T[K];
};

18242-All

数组中所有元素都等于 U

  1. 每项递归
  2. 如果存在一项不相等,结束递归返回 false
type All<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? StrictEquals<F, U> extends true
      ? All<Rest, U>
      : false
    : true;

18220-Filter

接受一个联合类型U,仅保留符合U的值

  1. 递归处理一下就好
type Filter<T extends unknown[], U> =
  T extends [infer F, ...infer Rest]
    ? F extends U
      ? [F, ...Filter<Rest, U>]
      : [...Filter<Rest, U>]
    : T;

21104-FindAll

给定字符串T,字符串U,返回索引数组,满足以U开头的下标

  1. 判断是否以某字符串开头
  2. Count["length"] 对下标计数、R 存下标结果、U_0 代表当前拆解处理的字符
  3. 逐个处理,如果首个字符相同,判断剩余部分是否相同。如果相同则存下标
  4. 递归处理每个字符
type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false;

type StringFirst<T extends string> = T extends `${infer First}${string}` ? First : never;

type FindAll<
  T extends string,
  U extends string,
  Count extends unknown[] = [],
  R extends number[] = [],
  U_0 extends string = StringFirst<U>,
> = 
  T extends `${infer F}${infer Rest}`
    ? F extends U_0
      ? StartsWith<T, U> extends true
	      // 存下标
        ? FindAll<Rest, U, [...Count, unknown], [...R, Count['length']]>
        // 透传,处理下一个字符
        : FindAll<Rest, U, [...Count, unknown], R>
      : FindAll<Rest, U, [...Count, unknown], R>
    : R;

21106-组合键类型 Combination key type

示例:前一个key可以组合后面所有的key,后面的key不能组前面的key

type ModifierKeys = ['cmd', 'ctrl', 'opt', 'fn']
type CaseTypeOne = 'cmd ctrl' | 'cmd opt' | 'cmd fn' | 'ctrl opt' | 'ctrl fn' | 'opt fn'
  1. 实现一个组合单个 key 的方法 ComboOne(联合起来,递归拼接)
  2. 递归调用 ComboOne 联合起来
type ComboOne<K1 extends string, KList extends string[]> =
  KList extends [infer K2 extends string, ...infer KRest extends string[]]
    ? `${K1} ${K2}` | ComboOne<K1, KRest>
    : never;

type Combs<T extends string[]> =
  T extends [infer K1 extends string, ...infer KRest extends string[]]
    ? ComboOne<K1, KRest> | Combs<KRest>
    : never;

25170-Replace First

替换第一个出现的 X,变成 Y

type ReplaceFirst<T extends unknown[], X, Y> =
  T extends [infer F, ...infer Rest]
    ? F extends X
      ? [Y, ...Rest]
      : [F, ...ReplaceFirst<Rest, X, Y>]
    : [];

27862-CartesianProduct

输入两个联合类型,输出联合类型的各种组合

示例

CartesianProduct<1 | 2, 'a' | 'b'> 
// [1, 'a'] | [2, 'a'] | [1, 'b'] | [2, 'b']
  1. 利用分布式计算处理
  2. 如上,我们需要构造:1|2 extends [”a”]1|2 extends [”b”]
  3. 实现一个值联合类型转[值]数组联合 UnionToTupleUnion
// 值联合 转 [值]联合
// UnionToTupleUnion<2 | 3 | 4> -> [2] | [3] | [4]
type UnionToTupleUnion<T> = T extends T ? [T] : never;

type CartesianProduct<T, U> = T extends T ? [T, ...UnionToTupleUnion<U>] : never;

27958-CheckRepeatedTuple

查找元组中有无重复的项

type CheckRepeatedTuple<T extends unknown[]> =
  T extends [infer F, ...infer Rest]
    ? Includes<Rest, F> extends true
      ? true
      : CheckRepeatedTuple<Rest>
    : false;

28333-Public Type

去除 “_” 开头的 key

type PublicType<T extends object> = {
  [K in keyof T as
    K extends string
      ? `${K}` extends `_${string}`
        ? never
        : K
      : K
  ]: T[K];
};

29650-ExtractToObject

拆对象的一层key,合并到上层

type ExtractToObject<T extends Record<string, any>, U extends keyof T> = {
  [K in (keyof Omit<T, U> | keyof T[U])]: K extends keyof T ? T[K] : T[U][K];
};

30301-IsOdd

判断是奇数

  1. 排除小数
  2. 判断是否以 13579 结尾
type IsOdd<T extends number> =
  `${T}` extends `${number}.${number}` | `${number}e${number}`
    ? false
    : `${T}` extends `${number | ''}${1 | 3 | 5 | 7 | 9}`
      ? true
      : false;

29785-Deep Omit

接受的 Path 可能以 “.” 分隔

  1. 遍历对象key,如果key是path一部分,则继续递归,否则返回原本值
  2. 递归到头,使用Omit剔除最内层对象
type DeepOmit<T, Path extends string> =
  Path extends `${infer K}.${infer Rest}`
    ? {
      [_K in keyof T]: _K extends K ? DeepOmit<T[_K], Rest> : T[_K]
    }
    : Omit<T, Path>;

34007-Compare Array Length

比较数组的长度,谁先被砍完谁短

type CompareArrayLength<T extends unknown[], U extends unknown[]> =
  T extends [infer _, ...infer TRest,]
    ? U extends [infer _, ...infer URest]
      ? CompareArrayLength<TRest, URest>
      : 1
    : U extends []
      ? 0
      : -1;

35191-Trace

获取二维数组斜对角的值联合类型

type Trace<
  T extends unknown[][],
  Count extends unknown[] = [],
  R extends any = T[0][0],
> =
  Count['length'] extends T['length']
    ? R
    : Trace<T, [...Count, unknown], R | T[Count['length']][Count['length']]>;

35252-IsAlphabet

判断是否为26英文字母

  1. 一个比较妙的做法,如果非英文字母,使用内置大小写转换结果一致,英文字母则不一致
  2. 如果是英文 "A" extends "a" 如果非英文 "-" extends "-"
type IsAlphabet<S extends string> = Uppercase<S> extends Lowercase<S> ? false : true;

35991-MyUppercase

实现内置 Uppercase

interface LetterDict {
  a: 'A';
  b: 'B';
  c: 'C';
  d: 'D';
  e: 'E';
  f: 'F';
  g: 'G';
  h: 'H';
  i: 'I';
  j: 'J';
  k: 'K';
  l: 'L';
  m: 'M';
  n: 'N';
  o: 'O';
  p: 'P';
  q: 'Q';
  r: 'R';
  s: 'S';
  t: 'T';
  u: 'U';
  v: 'V';
  w: 'W';
  x: 'X';
  y: 'Y';
  z: 'Z';
};

type MyUppercase<T extends string> =
  T extends `${infer F}${infer Tail}`
    ? F extends keyof LetterDict
      ? `${LetterDict[F]}${MyUppercase<Tail>}`
      : `${F}${MyUppercase<Tail>}`
    : T;