CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • TypeScript类型梳理
  • TypeScript联合、交叉类型解读
  • TypeScript枚举用法解读
  • keyof用法梳理
  • extends用法梳理
  • type-challenges笔记
    • 简单题 Eazy
      • 实现 Pick
      • 实现 Readonly
      • 实现 Tuple to Object
      • 第一个元素 First of Array
      • 获取元组长度 Length of Tuple
      • Exclude
      • Awaited
      • Awaited
      • If
      • Concat
      • Includes
      • Push
      • Unshift
      • Parameters
    • 中等题 Medium
      • 获取函数返回类型 Get Return Type
      • 实现 Omit
      • Readonly 2
      • Deep Readonly
      • 元组转合集Tuple to Union
      • 可串联构造器Chainable Options
      • 最后一个元素Last of Array
      • 出堆Pop
      • Promise.all
      • Type Lookup
      • Trim Left
      • Trim
      • Capitalize
      • Replace
      • ReplaceAll
      • 追加参数Append Argument
      • Permutation
      • Length of String
      • Flatten
      • Append to object
      • Absolute
      • String to Union
      • Merge
      • KebabCase
      • Diff
      • AnyOf
      • IsNever
      • IsUnion
      • ReplaceKeys
      • Remove Index Signature
      • Percentage Parser
      • Drop Char
      • MinusOne
      • PickByType
      • StartsWith
      • EndsWith
      • PartialByKeys
      • RequiredByKeys
      • Mutable
      • OmitByType
      • ObjectEntries
      • Shift
      • Tuple to Nested Object
      • Reverse
      • Flip Arguments
      • FlattenDepth
      • BEM style string
      • 中序遍历InorderTraversal
      • Flip
      • 斐波那契Fibonacci Sequence
      • 所有组合AllCombinations
  • TypeScript笔记
CD_wOw
2022-08-31
目录

type-challenges笔记

TypeScript 类型体操集合题库 (opens new window)

# 简单题 Eazy

# 实现 Pick

实现 TS 内置的 Pick<T, K>,但不可以使用它。

从类型 T 中选择出属性 K,构造成一个新的类型。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = MyPick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

// 解
type MyPick<T, K extends keyof T> = {
  [U in K]: T[U];
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现 Readonly

不要使用内置的Readonly,自己实现一个。

该 Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰。

也就是不可以再对该对象的属性赋值。

interface Todo {
  title: string;
  description: string;
}

const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar",
};

todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property

// 解
type MyReadonly<T> = {
  readonly [U in keyof T]: T[U];
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现 Tuple to Object

不要使用内置的Readonly,自己实现一个。

该 Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰。

也就是不可以再对该对象的属性赋值。

const tuple = ["tesla", "model 3", "model X", "model Y"] as const;

type result = TupleToObject<typeof tuple>; // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

// 解
type TupleToObject<T extends readonly any[]> = {
  [K in T[number]]: K;
};
1
2
3
4
5
6
7
8

# 第一个元素 First of Array

实现一个通用First,它接受一个数组T并返回它的第一个元素的类型。

type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];

type head1 = First<arr1>; // expected to be 'a'
type head2 = First<arr2>; // expected to be 3

// 解
type First<T extends any[]> = T[0];

1
2
3
4
5
6
7
8
9

# 获取元组长度 Length of Tuple

创建一个通用的Length,接受一个readonly的数组,返回这个数组的长度。

type tesla = ["tesla", "model 3", "model X", "model Y"];
type spaceX = [
  "FALCON 9",
  "FALCON HEAVY",
  "DRAGON",
  "STARSHIP",
  "HUMAN SPACEFLIGHT"
];

type teslaLength = Length<tesla>; // expected 4
type spaceXLength = Length<spaceX>; // expected 5
// 解
type Length<T extends any[]> = T["length"];
1
2
3
4
5
6
7
8
9
10
11
12
13

# Exclude

实现内置的Exclude <T, U>类型,但不能直接使用它本身。从联合类型T中排除U的类型成员,来构造一个新的类型

Extends在ts中的作用 (opens new window), 即当条件类型作用于泛型类型时,联合类型会被拆分使用。即 MyExclude<'a' | 'b' | 'c', 'a'> 会被拆分为 'a' extends 'a'、'b' extends 'a'、'c' extends 'a'

type Result = MyExclude<"a" | "b" | "c", "a">; // 'b' | 'c'
// 解
type MyExclude<T, K> = T extends K ? never : T;

1
2
3
4

如果不想遍历泛型中的每一个类型,可以用方括号将泛型给括起来以表示使用该泛型的整体部分。

type MyExclude<T, U> = [T] extends [U] ? never : T

// result 此时类型为 'a' | 'b' | 'c'
type result = MyExclude<'a' | 'b' | 'c', 'a'>
1
2
3
4

# Awaited

假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。

例如:Promise,请你返回 ExampleType 类型。

type ExampleType = Promise<string>;

type Result = MyAwaited<ExampleType>; // string
// 解
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
1
2
3
4
5

# Awaited

假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。

例如:Promise,请你返回 ExampleType 类型。

type ExampleType = Promise<string>;

type Result = MyAwaited<ExampleType>; // string
// 解
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
1
2
3
4
5

# If

实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 C 只能是 true 或者 false, T 和 F 可以是任意类型。

type A = If<true, "a", "b">; // expected to be 'a'
type B = If<false, "a", "b">; // expected to be 'b'
// 解
type If<T extends boolean, U, K> = T extends true ? U : K;

1
2
3
4
5

# Concat

在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

type Result = Concat<[1], [2]>; // expected to be [1, 2]
// 解
type Concat<T extends (keyof any)[], U extends (keyof any)[]> = [...T, ...U];
1
2
3

# Includes

在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false。

type isPillarMen = Includes<["Kars", "Esidisi", "Wamuu", "Santana"], "a">; // expected to be `false`
// 解
type Includes<T extends any[], U> = T extends [infer F, ...(infer R)]
  ? F extends U
    ? true
    : Includes<R, U>
  : false;
1
2
3
4
5
6
7

# Push

在类型系统里实现通用的 Array.push 。

type Result = Push<[1, 2], "3">; // [1, 2, '3']
// 解
type Push<T extends any[], U> = [...T, U];

1
2
3
4

# Unshift

实现类型版本的 Array.unshift。

type Result = Unshift<[1, 2], 0>; // [0, 1, 2,]
// 解
type Unshift<T extends unknown[], U> = [U, ...T];

1
2
3
4

# Parameters

实现内置的 Parameters 类型,而不是直接使用它

const foo = (arg1: string, arg2: number): void => {};

type FunctionParamsType = MyParameters<typeof foo>; // [arg1: string, arg2: number]
// 解
type MyParameters<T> = T extends (...args: infer K) => unknown ? K : never;

1
2
3
4
5
6

# 中等题 Medium

# 获取函数返回类型 Get Return Type

不使用 ReturnType 实现 TypeScript 的 ReturnType 泛型。

const fn = (v: boolean) => {
  if (v) return 1;
  else return 2;
};

type a = MyReturnType<typeof fn>; // 应推导出 "1 | 2"
// 解
type MyReturnType<T> = T extends (...args: any) => infer K ? K : never;

1
2
3
4
5
6
7
8
9

# 实现 Omit

不使用 Omit 实现 TypeScript 的 Omit<T, K> 泛型。

Omit 会创建一个省略 K 中字段的 T 对象。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = MyOmit<Todo, "description" | "title">;

const todo: TodoPreview = {
  completed: false,
};

// 解
type ExcludeByOmit<T, K extends T> = T extends K ? never : T;

type MyOmit<T, K extends keyof T> = {
  [U in ExcludeByOmit<keyof T, K>]: T[U];
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Readonly 2

实现一个通用MyReadonly2<T, K>,它带有两种类型的参数T和K。

K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly一样。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

const todo: MyReadonly2<Todo, "title" | "description"> = {
  title: "Hey",
  description: "foobar",
  completed: false,
};

todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property
todo.completed = true; // OK

// 解
type ExcludeByReadonly<T, K> = T extends K ? never : T;
type MyReadonly2<T, K extends keyof T> = {
  [U in ExcludeByReadonly<keyof T, K>]: T[U];
} & {
  readonly [V in K]: T[K];
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# Deep Readonly

实现一个通用的DeepReadonly,它将对象的每个参数及其子对象递归地设为只读。

您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。

type X = {
  x: {
    a: 1;
    b: "hi";
  };
  y: "hey";
  z: () => void;
};

type Expected = {
  readonly x: {
    readonly a: 1;
    readonly b: "hi";
  };
  readonly y: "hey";
};
type Todod = DeepReadonly<X>; // should be same as `Expected`

// 解
type ExcludeByReadonly<T, K> = T extends K ? never : T;
type DeepReadonly2<T> = {
  readonly [U in keyof T]: keyof T[U] extends never ? T[U] : DeepReadonly<T[U]>;
};
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends Function
    ? T[P]
    : T[P] extends Object
    ? DeepReadonly<T[P]>
    : T[P];
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 元组转合集Tuple to Union

实现泛型TupleToUnion,它返回元组所有值的合集。

type Arr = ["1", "2", "3"];

type Test = TupleToUnion<Arr>; // expected to be '1' | '2' | '3'
// 解
type TupleToUnion<T extends unknown[]> = T[number];
1
2
3
4
5

# 可串联构造器Chainable Options

在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?

在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。

const result: ResultChainable = config
  .option("foo", 123)
  .option("name", "type-challenges")
  .option("bar", { value: "Hello World" })
  .get();

// 期望 result 的类型是:
interface ResultChainable {
  foo: number;
  name: string;
  bar: {
    value: string;
  };
}
//解
type Chainable<T = {}> = {
  option<K extends string, V>(
    key: K,
    value: K extends keyof T ? (V extends T[K] ? never : V) : V
  ): Chainable<Omit<T, K> & Record<K, V>>;
  get(): T;
};
type Chainable2<O = {}> = {
  option<K extends string, V>(
    key: K extends keyof O ? never : K,
    value: V
  ): Chainable<{ [key in K]: V } & O>;
  get(): O;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 最后一个元素Last of Array

实现一个通用Last,它接受一个数组T并返回其最后一个元素的类型。

type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];

type tail1 = Last<arr1>; // expected to be 'c'
type tail2 = Last<arr2>; // expected to be 1

// 解
type Last<T extends unknown[]> = T extends [...unknown[], infer K] ? K : never;
1
2
3
4
5
6
7
8

# 出堆Pop

实现一个通用Pop,它接受一个数组T,并返回一个由数组T的前length-1项以相同的顺序组成的数组。 同样,您也可以实现Shift,Push和Unshift吗?

type arr1 = ["a", "b", "c", "d"];
type arr2 = [3, 2, 1];

type re1 = Pop<arr1>; // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2>; // expected to be [3, 2]
type re3 = Shift<arr1>; // expected to be  ["b", "c", "d"]
type re4 = Push<arr1, "e">; // expected to be ["a", "b", "c", "d", "e"]
type re5 = Unshift<arr1, "e">; // expected to be ["e", "a", "b", "c", "d"]
// 解
type Pop<T extends unknown[]> = T extends [...infer K, unknown] ? K : never;
type Shift<T extends unknown[]> = T extends [unknown, ...infer K] ? K : never;
type Push<T extends unknown[], K> = [...T, K];
type Unshift<T extends unknown[], K> = [K, ...T];

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Promise.all

键入函数PromiseAll,它接受PromiseLike对象数组,返回值应为Promise,其中T是解析的结果数组。

const promise1 = Promise.resolve(3);
const promise2 = 43;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, "foo");
});

// expected to be `Promise<[number, 42, string]>`
const d = PromiseAll([promise1, promise2, promise3] as const);

// 解
declare function PromiseAll<T extends unknown[]>(
  values: readonly [...T]
): Promise<{
  [K in keyof T]: T[K] extends Promise<infer U> ? U : T[K];
}>;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Type Lookup

有时,您可能希望根据某个属性在联合类型中查找类型。

在此挑战中,我们想通过在联合类型Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp<Dog | Cat, 'dog'>获得Dog,LookUp<Dog | Cat, 'cat'>获得Cat。

interface Cat {
  type: "cat";
  breeds: "Abyssinian" | "Shorthair" | "Curl" | "Bengal";
}

interface Dog {
  type: "dog";
  breeds: "Hound" | "Brittany" | "Bulldog" | "Boxer";
  color: "brown" | "white" | "black";
}

type MyDog = LookUp<Cat | Dog, "dog">; // expected to be `Dog`

// 解
type LookUp<T, K> = T extends { type: K } ? T : never;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Trim Left

实现 TrimLeft ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。

type trime = TrimLeft<"  Hello World  ">; // 应推导出 'Hello World  '

// 解
type EmptyString = " " | "\t" | "\n";
type TrimLeft<T extends string> = T extends `${EmptyString}${infer U}`
  ? TrimLeft<U>
  : T;
1
2
3
4
5
6
7

# Trim

实现Trim,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。

type trime2 = Trim<"  Hello World  ">; // expected to be 'Hello World'
// 解
type EmptyString = " " | "\t" | "\n";
type Trim<T extends string> = T extends `${EmptyString}${infer U}${EmptyString}`
  ? Trim<U>
  : T;
1
2
3
4
5
6

# Capitalize

实现 Capitalize 它将字符串的第一个字母转换为大写,其余字母保持原样。

type capitalize = Capitalize<"hello world">; // expected to be 'Hello world'
// 解
type Capitalize<T extends string> = T extends `${infer U}${infer K}`
  ? `${Uppercase<U>}${K}`
  : T;

1
2
3
4
5
6

额外的,Ts的Uppercase和Loowercase

/**
 * Convert string literal type to uppercase
 */
type Uppercase<S extends string> = intrinsic;

/**
 * Convert string literal type to lowercase
 */
type Lowercase<S extends string> = intrinsic;
1
2
3
4
5
6
7
8
9

# Replace

实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。

type replaced = Replace<"types are fun!", "fun", "awesome">; // 期望是 'types are awesome!'
// 解
type Replace<
  S,
  From extends string,
  To extends string
> = S extends `${infer T}${From}${infer F}` ? `${T}${To}${F}` : S;
1
2
3
4
5
6
7

# ReplaceAll

实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。

type replaced = ReplaceAll<"t y p e s", " ", "">; // 期望是 'types'
// 解
type ReplaceAll<
  T extends string,
  K extends string,
  U extends string
> = T extends `${infer F}${K}${infer E}` ? ReplaceAll<`${F}${E}`, K, U>: T;

type ReplaceAll2<
  T extends string,
  K extends string,
  U extends string
> = K extends '' ? T : T extends `${infer F}${K}${infer E}` ? `${F}${U}${ReplaceAll<E, K, U>}`: T;

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 追加参数Append Argument

实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

type Fn = (a: number, b: string) => number;

type Result = AppendArgument<Fn, boolean>;
// 期望是 (a: number, b: string, x: boolean) => number
// 解
type AppendArgument<T extends Function, K> = T extends (
  ...args: infer V
) => infer U
  ? (...args: [...V, K]) => U
  : never;

// 额外的,回顾Parameters,获取函数的参数
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Permutation

实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

type perm = Permutation<"A" | "B" | "C">; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
// 解
type Permutation<T extends keyof any> = [T] extends [never]
  ? []
  : {
      [TT in T]: [TT, ...Permutation<Exclude<T, TT>>];
    }[T];

1
2
3
4
5
6
7
8

这相当于由

['A', ...Permutation<Exclude<T, 'A'>>] | ['B', ...Permutation<Exclude<T, 'B'>>] | ['C', ...Permutation<Exclude<T, 'C'>>]
1

到

['A', ...Permutation<'B' | 'C'>] | ['B', ...Permutation<'A' | 'C'>] | ['C', ...Permutation<'A' | 'B'>]
1

# Length of String

计算字符串的长度,类似于 String#length 。

type len = StringLength<"asdasdsa">; // 8
// 解
type StringLength<
  T extends string,
  L extends unknown[] = []
> = T extends `${infer F}${infer E}` ? StringLength<E, [...L, F]> : L["length"];
1
2
3
4
5
6

# Flatten

在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。

type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, 5]
// 解
type Flatten<T extends unknown[]> = T extends [infer A, ...infer B]
  ? A extends unknown[]
    ? Flatten<[...A, ...B]>
    : [A, ...Flatten<[...B]>]
  : T;
1
2
3
4
5
6
7

# Append to object

实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。

type Test = { id: "1" };
type Result = AppendToObject<Test, "value", 4>; // expected to be { id: '1', value: 4 }
// 解
type AppendToObject<T extends {}, K extends string, V> = {
  [k in keyof T]: T[k];
} & {
  [v in K]: V;
};
// 解2
type AppendToObject<T, K extends keyof any, V> = {
  [key in keyof T | K]: key extends keyof T ? T[key] : V;
};
1
2
3
4
5
6
7
8
9
10
11
12

# Absolute

实现一个接收string,number或bigInt类型参数的Absolute类型,返回一个正数字符串。

type Test = -100;
type Result = Absolute<Test>; // expected to be "100"
// 解
type Absolute<T extends string | number | bigint> = `${T}` extends `${infer I}${infer U}` ?  I extends '-' ?  Absolute<U> : T : T;
1
2
3
4

# String to Union

实现一个将接收到的String参数转换为一个字母Union的类型。

type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
// 解
type StringToUnion<T extends string, K = never> = T extends `${infer V}${infer U}`? V extends never ? K : StringToUnion<U, K | V> : K;
1
2
3
4

# Merge

将两个类型合并成一个类型,第二个类型的键会覆盖第一个类型的键。

type foo = {
  name: string;
  age: string;
};

type coo = {
  age: number;
  sex: string;
};

type Result = Merge2<foo, coo>; // expected to be {name: string, age: number, sex: string}
// 解
type Excludes<T, K> = T extends K ? never : T;
type Merge<T, K> = {
  [V in Excludes<keyof T, keyof K>]: T[V];
} &
  K;

type Merge2<T, K> = {
  [V in keyof T | keyof K]: V extends keyof K
    ? K[V]
    : V extends keyof T
    ? T[V]
    : never;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# KebabCase

FooBarBaz -> foo-bar-baz

type Result = KebabCase<"FooBarBaz">; // FooBarBaz -> foo-bar-baz
// 解
// Uncapitalize:  将字符串文字类型的第一个字符转换为小写
type KebabCase<T extends string> = T extends `${infer First}${infer Rest}`
? Rest extends Uncapitalize<Rest> 
? `${Lowercase<First>}${KebabCase<Rest>}` 
: `${Lowercase<First>}-${KebabCase<Rest>}`
: T;
// 统一Lowercase的目的是将字符串内的字符全部变成小写(因为目的就是大写的拆分成单词
1
2
3
4
5
6
7
8
9

# Diff

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

type Foo = {
  a: string;
  b: number;
};
type Bar = {
  a: string;
  c: boolean;
};

type Result1 = Diff<Foo, Bar>; // { b: number, c: boolean }
type Result2 = Diff<Bar, Foo>; // { b: number, c: boolean }
// Foo & Bar  {a:string b:number c:boolean}
// 解
type Diff<T, U> = {
  [key in Exclude<keyof (T & U), keyof (T | U)>]: key extends keyof T
    ? T[key]
    : key extends keyof U
    ? U[key]
    : never;
};

// 或者这样写
type Diff2<T, U> = {
  [key in Exclude<keyof (T & U), keyof (T | U)>]: (T & U)[key];
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 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 Fasly = "" | 0 | false | [] | { [p: string]: never };
type AnyOf<T extends readonly any[]> = T extends Array<Fasly> ? false : true;
1
2
3
4
5

# IsNever

类型IsNever,输入泛型T,如果输入的内容是never则返回true,否则返回false

type A = IsNever<never>; // expected to be true
type B = IsNever<undefined>; // expected to be false
type C = IsNever<null>; // expected to be false
type D = IsNever<[]>; // expected to be false
type E = IsNever<number>; // expected to be false

// 解

type IsNever<T extends any> = [T] extends [never] ? true : false;
1
2
3
4
5
6
7
8
9

# IsUnion

实现一个类型IsUnion,它接受输入类型T并返回T是否解析为union类型。

type case1 = IsUnion<string>; // false
type case2 = IsUnion<string | number>; // true
type case3 = IsUnion<[string | number]>; // false
// 解
type IsNever<T> = [T] extends [never] ? true : false;

type IsUnion<T, S = T> = IsNever<T> extends true ? false : T extends S ? [S] extends [T] ? false : true: false;

1
2
3
4
5
6
7
8

# ReplaceKeys

实现一个类型 ReplaceKeys,它替换联合类型中的键,如果某些类型没有这个键,则跳过替换,这个类型需要三个参数。

type NodeA = {
  type: "A";
  name: string;
  flag: number;
};

type NodeB = {
  type: "B";
  id: number;
  flag: number;
};

type NodeC = {
  type: "C";
  name: string;
  flag: number;
};

type Nodes = NodeA | NodeB | NodeC;

type ReplacedNodes = ReplaceKeys<
  Nodes,
  "name" | "flag",
  { name: number; flag: string }
>; // {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string} // would replace name from string to number, replace flag from number to string.

type ReplacedNotExistKeys = ReplaceKeys<Nodes, "name", { aa: number }>; // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never

// 解
type ReplaceKeys<T, K, U> = {
  [t in keyof T]: t extends K ? (t extends keyof U ? U[t] : never) : T[t];
};

type ReplaceKeys2<U, T, Y> = U extends any ? {
  [P in keyof U]: P extends T ? P extends keyof Y ? Y[P] : never : U[P];
} : never;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

方法1 和方法2的区别仅在于是否利用了联合类型的特性将其解构重组

# Remove Index Signature

删除索引签名 实施RemoveIndexSignature,从对象类型中排除索引签名。

type Foo = {
  [key: string]: any;
  1:1
  foo(): void;
};

type A = RemoveIndexSignature<Foo>; // expected { foo(): void }

// 解
// 思路:
// 当键为泛型定义时, keyof返回的是泛型定义的类型,如上keyof为 string | 1 | foo
// 我们只需要去甄别键是否属于接口类型键的number|string|symbol即可

type RemoveIndexSignature<T> = {
  [K in keyof T as (number extends K ? never : symbol extends K ? never : string extends K ? never : K)]: T[K]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Percentage Parser

实现类型 PercentageParser。根据规则 /^(+|-)?(\d*)?(%)?$/ 匹配类型 T。

匹配的结果由三部分组成,分别是:[正负号, 数字, 单位],如果没有匹配,则默认是空字符串。

type PString1 = ''
type PString2 = '+85%'
type PString3 = '-85%'
type PString4 = '85%'
type PString5 = '85'

type R1 = PercentageParser<PString1> // expected ['', '', '']
type R2 = PercentageParser<PString2> // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3> // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4> // expected ["", "85", "%"]
type R5 = PercentageParser<PString5> // expected ["", "85", ""]

// 解
// 思路,借助了第二个参数U来辅助保存每一次遍历时存储的值
 type PercentageParser<A extends string, U extends [string, string, string] = ['', '', '']> = A extends `${infer T}${infer K}` ?
    T extends '+' | '-' ? PercentageParser<K, [T, U[1], U[2]]>
        : T extends '%' ? PercentageParser<K, [U[0], U[1], '%']>
            : PercentageParser<K, [U[0], `${U[1]}${T}`, U[2]]>
    : U
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Drop Char

从字符串中剔除指定字符。

type Butterfly = DropChar<" b u t t e r f l y ! ", " ">; // 'butterfly!'

// 解
type DropChar<
  T extends string,
  K extends string
> = T extends `${infer B}${infer C}`
  ? B extends K
    ? DropChar<C, K>
    : `${B}${DropChar<C, K>}`
  : T;

type DropChar2<
  T extends string,
  K extends string,
  S extends string = ""
> = T extends `${infer B}${infer C}`
  ? B extends K
    ? DropChar<C, K, S>
    : DropChar<C, K, `${S}${B}`>
  : S;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# MinusOne

给定一个正整数作为类型的参数,要求返回的类型是该数字减 1。

type Zero = MinusOne<1>; // 0
type FiftyFour = MinusOne<55>; // 54

// 解
// 思路: 通过数组长度和输入的数字相匹配的原则将数组长度添加至等同输入的数字
// 再通过infer分配取出length - 1 的长度
type Num<N extends number, R extends any[] = []> = R["length"] extends N
  ? R
  : Num<N, [...R, 1]>;

type MinusOne<T extends number> = Num<T> extends [infer A, ...infer B]
  ? A extends number
    ? B["length"]
    : 0
  : 0;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

看似这道题解出来了,实际上对于ts而言 搭配的数组数字不超过1000, 当超过1000时会发生这样的报错

type FiftyFour = MinusOne<1000>;
// ts error: 类型实例化过深,且可能无限
1
2

我翻阅了后续很多答案,发现里面的答案都很繁琐冗长,其实个人感觉没有这个必要,不如定义一个number来的实在。

# PickByType

从T中选择一组属性,其类型可分配给U。

type OnlyBoolean = PickByType<
  {
    name: string;
    count: number;
    isReadonly: boolean;
    isEnable: boolean;
  },
  boolean
>; // { isReadonly: boolean; isEnable: boolean; }

// 解

type PickByType<T extends Record<any, any>, K> = {
  [u in keyof T as T[u] extends K ? u : never]: T[u];
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# StartsWith

实现StartsWith<T, U>,接收两个string类型参数,然后判断T是否以U开头,根据结果返回true或false

type a = StartsWith<"abc", "ac">; // expected to be false
type b = StartsWith<"abc", "ab">; // expected to be true
type c = StartsWith<"abc", "abcd">; // expected to be false

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

type StartsWith2<
  T extends string,
  K extends string,
  Head extends string = ""
> = T extends `${infer F}${infer U}`
  ? `${Head}${F}` extends K
    ? true
    : StartsWith2<U, K, `${Head}${F}`>
  : false;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

通常的,当需要甄别的元素为string类型时,我们常常通过 extends ${xxx} 或是 extends ${infer X} 来展开。

# EndsWith

实现EndsWith<T, U>,接收两个string类型参数,然后判断T是否以U结尾,根据结果返回true或false

type a = EndsWith<"abc", "bc">; // expected to be true
type b = EndsWith<"abc", "abc">; // expected to be true
type c = EndsWith<"abc", "d">; // expected to be false
// 解

type EndsWith<T extends string, K extends string> = T extends `${string}${K}`
  ? true
  : false;
1
2
3
4
5
6
7
8

# PartialByKeys

实现一个通用的PartialByKeys<T, K>,它接收两个类型参数T和K。

K指定应设置为可选的T的属性集。当没有提供K时,它就和普通的Partial一样使所有属性都是可选的。

interface User {
  name: string;
  age: number;
  address: string;
}

type UserPartialName = PartialByKeys<User, "name">; // { name?:string; age:number; address:string }
// 解

type PartialByKeys<
  T extends Record<any, any>,
  K extends keyof T
> = K extends never
  ? {
      [V in keyof T]?: T[V];
    }
  : {
      [V in keyof T as V extends K ? V : never]?: T[V];
    } & {
      [V in keyof T as V extends K ? never : V]: T[V];
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# RequiredByKeys

实现一个通用的RequiredByKeys<T, K>,它接收两个类型参数T和K。

K指定应设为必选的T的属性集。当没有提供K时,它就和普通的Required一样使所有的属性成为必选的。

interface User {
  name?: string;
  age?: number;
  address?: string;
}

type UserRequiredName = RequiredByKeys<User, "name">; // { name: string; age?: number; address?: string }
// 解
type Copy<T> = Pick<T, keyof T>;
type RequiredByKeys<T, K = keyof T> = Copy<
  Omit<T, K & keyof T> & Required<Pick<T, K & keyof T>>
>;

1
2
3
4
5
6
7
8
9
10
11
12
13

这题我使用了原有的类型支持来做这道题,这里使用Omit生成除了‘name’以外的属性的接口类型。这里的 K & keyof T 则可以理解成交叉联合类型,可以理解为取交集,即输入‘name’时:'name' & (name|age|address) => 'name'。当为空白字符串输入时,则取的是User接口类型的属性。再通过Pick取出指定的属性Required使其成为必选的,当

{name?:string} & {name: string}
1

时,属性将会变为必选。最后使用Copy进行属性的铺平

# Mutable

实现一个通用的类型 Mutable,使类型 T 的全部属性可变(非只读)。

interface Todo {
  readonly title: string;
  readonly description: string;
  readonly completed: boolean;
}

type MutableTodo = Mutable<Todo>; // { title: string; description: string; completed: boolean; }
// 解
type Mutable<T extends Record<any, any>> = {
  -readonly [V in keyof T]: T[V];
};
1
2
3
4
5
6
7
8
9
10
11

# OmitByType

实现一个类型OmitByType<T,K>, 将类型T中的属性值为 K 的属性移除

type OmitBoolean = OmitByType<
  {
    name: string;
    count: number;
    isReadonly: boolean;
    isEnable: boolean;
  },
  boolean
>; // { name: string; count: number }
// 解
type OmitByType<T extends Record<any, any>, K> = {
  [V in keyof T as T[V] extends K ? never : V]: T[V];
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# ObjectEntries

实现 Object.entries 功能

interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}
type modelEntries = ObjectEntries<Model>; // ['name', string] | ['age', number] | ['locations', string[] | null];

// 解
type ObjectEntries<T extends Record<any, any>, U = keyof T> = U extends infer X
  ? [X, T[X]]
  : never;
1
2
3
4
5
6
7
8
9
10
11

# Shift

实现 Array.shift 功能

type Result = Shift<[3, 2, 1]>; // [2, 1]
// 解
type Shift<T extends unknown[]> = T extends [infer I, ...(infer K)] ? K : T;

type Shift2<T extends unknown[]> = T extends [infer I, ...(infer K)]
  ? I extends never
    ? T
    : K
  : T;
1
2
3
4
5
6
7
8
9

# Tuple to Nested Object

传入一个元组 和一个类型,将其转化为接口类型,里面的每一个属性都为元组内的元素,属性的类型为传入的类型,且元组内的第二个元素是第一个元素的子集

type a = TupleToNestedObject<["a"], string>; // {a: string}
type b = TupleToNestedObject<["a", "b"], number>; // {a: {b: number}}
type c = TupleToNestedObject<[], boolean>; // boolean. if the tuple is empty, just return the U type

// 解
// declare type PropertyKey = string | number | symbol;
type TupleToNestedObject<T extends PropertyKey[], K> = T extends [infer U, ...infer F extends PropertyKey[]] ? {
  [k in PropertyKey & U]: TupleToNestedObject<F, K>
}:K

type TupleToNestedObject<T extends Array<unknown>, U> = T extends [
  infer F,
  ...(infer R)
]
  ? {
      [K in F & PropertyKey]: TupleToNestedObject<R, U>;
    }
  : U;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Reverse

实现 Array.reverse

type a = Reverse<["a", "b"]>; // ['b', 'a']
type b = Reverse<["a", "b", "c", "d"]>; // ['c', 'b', 'a']

// 解

type Reverse<T extends unknown[]> = T extends [...infer A, infer B]
  ? [B, ...Reverse<A>]
  : T;
1
2
3
4
5
6
7
8

# Flip Arguments

实现lodash里的flip函数,TypeFlipArguments需要函数类型T并返回一个新的函数类型,它与 T 的返回类型相同,但参数相反。

type Flipped = FlipArguments<
  (arg0: string, arg1: number, arg2: boolean) => void
>;
// (arg0: boolean, arg1: number, arg2: string) => void
// 解
type Reverse<T extends unknown[]> = T extends [...infer A, infer B]
  ? [B, ...Reverse<A>]
  : T;
type FlipArguments<T extends (...args: any[]) => unknown> = T extends (
  ...args: infer A
) => infer K
  ? (...arg0: Reverse<A>) => K
  : Text;

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# FlattenDepth

深层数组结构拍平

type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>; // [1, 2, 3, 4, [5]]. flattern 2 times
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
// 解
type FlattenDepth<
  T extends any[],
  K extends number = 1,
  L extends any[] = []
> = L["length"] extends K
  ? T
  : T extends [infer F, ...infer U]
  ? F extends any[]
    ? [...FlattenDepth<F, K, [...L, 1]>, ...FlattenDepth<U, K, L>]
    : [F, ...FlattenDepth<U, K, L>]
  : T;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

ts 中需要比较具体的数字类型,通常都需要数组的 length 属性。那么我们可以增加一个数组类型参数 L,每次打平向它里面添加一个元素来达到 ”+1“ 的目的。然后每次递归时,判断层数和它的 length 是否一致,如果一致,说明打平层数够了,直接返回本身即可;否则继续递归。

# BEM style string

块、元素、修饰符方法 (BEM) 是 CSS 中类的流行命名约定。

例如,块组件将表示为btn,依赖于块的元素将表示为btn__price,改变块样式的修饰符将表示为btn--big或btn__price--warning。

实现BEM<B, E, M>从这三个参数生成字符串联合。B是字符串类型,E和M是字符串数组(可以为空)。

type CovertBEM<T extends string[], S extends string> = T extends [] ? '' : `${S}${T[number]}`

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

1
2
3
4

# 中序遍历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 TreeNode = {
  val: any;
  left: TreeNode | null;
  right: TreeNode | null;
};
type InorderTraversal<T extends TreeNode | null> = [T] extends [TreeNode]
  ? [...InorderTraversal<T["left"]>, T["val"], ...InorderTraversal<T["right"]>]
  : [];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Flip

实现接口类型的键与值互换

type a = Flip<{ a: "x"; b: "y"; c: "z" }>; // {x: 'a', y: 'b', z: 'c'}
type b = Flip<{ a: 1; b: 2; c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'}
type c = Flip<{ a: false; b: true }>; // {false: 'a', true: 'b'}
// 解
type Flip<T extends Record<any, any>> = {
  [K in keyof T as `${T[K]}`]: K;
};

1
2
3
4
5
6
7
8

# 斐波那契Fibonacci Sequence

实现斐波那契序列 Fibonacci 。传入数字T返回斐波那契数列在T下所对应的数字

The sequence starts: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...

type Result1 = Fibonacci<3>; // 2
type Result2 = Fibonacci<8>; // 21
// 解
// declare type PropertyKey = string | number | symbol;
type Fibonacci<
  T extends number,
  LayerCount extends any[] = [1],
  Pre extends any[] = [],
  Cur extends any[] = [1]
> = T extends 0
  ? 1
  : LayerCount["length"] extends T
  ? Cur["length"]
  : Fibonacci<T, [...LayerCount, 1], Cur, [...Pre, ...Cur]>;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 所有组合AllCombinations

实现返回所有最多AllCombinations使用一次字符的字符串组合的类型。

type AllCombinations_ABC = AllCombinations<"ABC">;
// should be '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
// 解
type string2union<S extends string>=S extends `${infer F}${infer K}`?F | string2union<K>:never;

type combinations<T extends string,U extends string=T>=[T] extends [never]?'':T extends U?`${T}${combinations<Exclude<U,T>>}` | `${combinations<Exclude<U,T>>}`:''

type AllCombinations<S extends string> = combinations<string2union<S>>
1
2
3
4
5
6
7
8
编辑 (opens new window)
#TypeScript
上次更新: 2023/03/22, 15:28:00
extends用法梳理

← extends用法梳理

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式