TS进阶题目

TS进阶题目

第一题

  • 以下代码为什么会提示错误,应该如何解决上述问题?
1
2
3
4
5
6
7
8
9
10
11
type User = {
id: number;
kind: string;
}

function createCustomer<T extends User>(u: T): T {
return {
id: u.id,
kind: 'customer'
}
}

第一种解决方案

1
2
3
4
5
6
7
8
9
10
11
type User = {
id: number;
kind: string;
}

function createCustomer<T extends User>(u: T): User {
return {
id: u.id,
kind: 'customer',
}
}

第二种解决方案

1
2
3
4
5
6
7
8
9
10
11
12
type User = {
id: number;
kind: string;
}

function createCustomer<t extends User>(u: T): T {
return {
...u,
id: u.id,
kind: 'customer'
}
}

第二题

  • 以下函数我们希望参数ab的类型都是一致的,即ab同时为numberstring类型,当它们的类型不一致时,ts 类型检查器能自动提示对应的错误信息。
1
2
3
4
5
6
7
8
9
10
11
12
function f(a: string | number,b: string | number) {
if (typeof a === 'string') {
return a + ':' + b;
} else {
return a + b;
}
}

f(1, 2); // OK
f('a', 'b'); // OK
f('a', 2); // Error
f(1, 'b'); //Error

第一种解决方案

  • 函数重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function f(a: string, b: string): string;
function f(a: number, b: number): number;
function f(a: string | number, b: string | number): string | number {
if (typeof a === 'string') {
return a + ':' + b;
} else {
return (a as number) + (b as number);
}
}

f(1, 2); // OK
f('a', 'b'); // OK
f(1, 'b'); // Error
f('a', 2); // Error

第二种解决方案

  • 自定义路由守卫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const isStringArray = (params: string[] | number[]): params is string[] => typeof params[0] === 'string';

function f(...args: string[] | number[]) {
if (isStringArray(args)) {
return args[0] + ':' + args[1];
} else {
return args[0] + args[1];
}
}

f(1, 2); // OK
f('a', 'b'); // OK
f(1, 'b'); // Error
f('a', 2); // Error

第三种解决方案

  • 范型
1
2
3
4
5
6
7
8
9
10
11
12
function f<T extends string | number>(a: T, b: T) {
if (typeof a === 'string') {
return a + ':' + b;
} else {
return (a as number) + (b as number);
}
}

f(1, 2); // OK
f('a', 'b'); // OK
f(1, 'b'); // Error
f('a', 2); // Error

第三题

  • 实现 SetOptional 工具类型,支持把给定的 keys 对应的属性变为可选,参考Partial
  • 实现 SetRequired 工具类型,支持把给定的 keys 对应的属性变成必填,参考 Require
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
// SetOptional 测试用例
type Foo = {
a: number;
b?: string;
c: boolean;
}

type SomeOptional = SetOptional<Foo, 'a' | 'b'>

type SomeOptional = {
a?: number; //该属性变为可选的
b?: string; //保持不变
c: boolean;
}

// SetRequired 测试用例
type Foo = {
a: number;
b?: string;
c: boolean;
}

type SomeRequired = SetRequired<Foo, 'a' | 'b'>

type SomeRequired = {
a: number; //保持不变
b: string; //该属性变为必选的
c: boolean;
}

解决方案 1

  • SetOptional
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Foo = {
a: number;
b?: string;
c: boolean;
}

// 对交叉类型进行扁平化处理
type Simplify<T> = {
[P in keyof T]: T[P]
}

type SetOptional<T, K extends keyof T> = Simplify<Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>>

type SomeOptional = SetOptional<Foo, 'a' | 'b'>
  • SetRequired
1
2
3
4
5
6
7
8
9
10
11
12
13
type Foo = {
a: number;
b?: string;
c: boolean;
}

type Simplify<T> = {
[P in keyof T]: T[P]
}

type SetRequired<T, K extends keyof T> = Simplify<Pick<T, Exclude<keyof T, K>> & Required<Pick<T, K>>>

type SomeRequired = SetRequired<Foo, 'b' | 'c'>

解决方案 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Foo = {
a: number;
b?: string;
c: boolean;
}

type Simplely<T> = {
[P in keyof T]: T[P]
}

type SetOptional<T, K extends keyof T> = Simplely<{ [X in keyof Omit<T, K>]: T[X]; } & { [P in K]?: T[P] }>;
type SetRequired<T, K extends keyof T> = Simplely<{ [X in keyof Omit<T, K>]: T[X]; } & { [P in K]-?: T[P] }>;


// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;

解决方案 3

1
2
3
4
5
6
7
type Simplify<T> = {
[P in keyof T]: T[P]
}

type SetOptional<T, K extends keyof T> = Simplify<Partial<Pick<T, K>> & Omit<T, K>>;

type SetRequired<T, K extends keyof T> = Simplify<Required<Pick<T, K>> & Omit<T, K>>

第四题

  • Pick<T, K extends keyof T> 的作用是将某个类型中的字属性挑出来,得到包含这个类型部分属性的字类型。
1
2
3
4
5
6
7
8
9
10
11
12
interface Todo {
title: string;
description: string;
completed: boolean;
}

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

const todo: TodoPreview = {
title: "clean room",
completed: false
}
  • 问题:如何定义一个ConditionalPick工具类型,支持根据指定的Condition条件来生成新的类型,对应的使用示例如下:
1
2
3
4
5
6
7
8
9
10
interface Example {
a: string;
b: string | number;
c: () => void;
d: {}
}

// 测试用例
type StringKeyOnly = ConditionalPick<Example, string>;
// => {a: string}

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}

type ConditionalPick<T, K> = {
[P in keyof T as (T[P] extends K ? P : never)]: T[P]
}

// 测试用例
type StringKeysOnly = ConditionalPick<Example, string>;
// => {a: string}

第五题

  • 定义一个工具类型AppendArgument,为已有函数累心增加指定类型的参数,新增的参数名是x,将作为新函数类型的第一个参数,示例如下:
1
2
3
4
5
6
type Fn = (a: number, b: string) => number;
type AppendArgument<F, A> = // 你的实现代码

// 测试用例
type FinalFn = AppendArgument<Fn, boolean>
// (x: boolean, a: number, b: string) => number;

解决方案 1

1
2
3
4
5
6
type AppendArgument<F extends (...args: any) => any, A> = (x: A, ...args: Parameters<F>) => ReturnType<F>

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

type FinalFn = AppendArgument<Fn, boolean>;
// (x: boolean, a: number, b: string) => number;

解决方案 2

1
2
3
4
5
6
type AppendArgument<F, T> = F extends (...args: infer Args) => infer Return ? (x: T, ...args: Args) => Return : never;

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

type FinalFn = AppendArgument<Fn, boolean>;
// (x: boolean, a: number, b: string) => number

第六题

  • 定义一个 NativeFlat 工具类型,支持把数组类型拍平(扁平化),示例如下:
1
2
3
4
5
type NativeFlat<T extends any[]> = // 实现代码

//测试用例
type NativeResult = NativeFlat<[['a'],['b','c'],['d']]>;
// => "a" | "b" | "c" | "d"
  • 在完成NativeFlat工具类型之后,继续实现DeepFlat工具类型,以支持多维数组类型:
1
2
3
4
5
6
type DeepFlat<T extends any[]> = unknown // 你的实现代码

// 测试用例
type Deep = [['a'], ['b', 'c'], [['d']], [[[['e']]]]];
type DeepTestResult = DeepFlat<Deep>
// DeepTestResult: "a" | "b" | "c" | "d" | "e"

解决方案

  • NativeFlat
1
2
3
4
5
6
type NaiveFlat<T extends any[]> = {
[P in keyof T]: T[P] extends any[] ? T[P][number] : T[P]
}[number]

type NaiveResult = NaiveFlat<[['a'], ['b', 'c'], ['d']]>
// NaiveResult的结果: "a" | "b" | "c" | "d"
  • DeepFlat
1
2
3
4
5
6
7
8
type Deep = [['a'], ['b', 'c'], [['d']], [[[['e']]]]];

type DeepFlat<T extends any[]> = {
[K in keyof T]: T[K] extends any[] ? DeepFlat<T[K]> : T[K]
}[number]

type DeepTestResult = DeepFlat<Deep>
// DeepTestResult: "a" | "b" | "c" | "d" | "e"

第七题

  • 使用类型别名定义一个EmptyObject类型,是的该类型只允许空对象赋值:
1
2
3
4
5
6
7
type EmptyObject = {}

// 测试用例
const shouldPass: EmptyObject = {}; // OK
const shouldFail: EmptyObject = { // Error
prop: "TS"
}
  • 更改以下takeSomeTypeOnly函数类型定义,让参数只允许严格 SomeType 类型的值,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
type SomeType = {
prop: string;
}

function takeSomeTypeOnly(x: SomeType) {return x};

// 测试用例
const x = {prop: 'a'};
takeSomeTypeOnly(x); // OK

const y = {prop: 'a', additionalProp: 'x'};
takeSomeTypeOnly(y); // Error

解决方案

  • EmptyObject
1
2
3
4
5
6
7
8
9
10
11
// type PropertyKey = string | number | symbol;

type EmptyObject = {
[K in PropertyKey]: never;
}

// 测试用例
const shouldPass: EmptyObject = {}; // OK
const shouldFail: EmptyObject = { // Error
prop: "TS"
}
  • takeSomeTypeOnly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type SomeType = {
prop: string;
}

type Exclusive<T1, T2 extends T1> = {
[K in keyof T2]: K extends keyof T1 ? T2[K] : never;
}

function takeSomeTypeOnly<T extends SomeType>(x: Exclusive<SomeType, T>) {return x};

// 测试用例
const x = {prop: 'a'};
takeSomeTypeOnly(x); // OK

const y = {prop: 'a', additionalProp: 'x'};
takeSomeTypeOnly(y); // Error

第八题

  • 定义一个NonEmptyArray工具类型,用于确保数据为非空数组
1
2
3
4
type NonEmptyArray<T> = // 代码实现

const err: NonEmptyArray<string> = []; // Error
const succ: NonEmptyArray<string> = ['Hello Ts']; //Ok

解决方案 1

1
2
3
4
type NonEmptyArray<T> = [T, ...T[]];

const err: NonEmptyArray<string> = []; // Error
const succ: NonEmptyArray<string> = ['Hello Ts']; //Ok

解决方案 2

1
2
3
4
5
type NonEmptyArray<T> = T[] & {0: T};

const err: NonEmptyArray<string> = []; // Error
const succ: NonEmptyArray<string> = ['Hello Ts']; //Ok

解决方案 3

1
2
3
4
5
6
7
8
type NonEmptyArray<T> = {
[P in number]: T;
} & {
0: T
};

const err: NonEmptyArray<string> = []; // Error
const succ: NonEmptyArray<string> = ['Hello Ts']; //Ok

第九题

  • 定义一个JoinStrArray工具类型,用于根据指定的Separator分隔符,对字符串数据类型进行拼接,示例如下:
1
2
3
4
5
6
7
type JoinStrArray<Arr extends string[], Separator extends string, Result extends string = ""> = // 你的实现代码

// 测试用例
type Names = ["Sem", "Lolo", "Kaquko"]
type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"
type NamesSpace = JoinStrArray<Names, " "> // "Sem Lolo Kaquko"
type NamesStars = JoinStrArray<Names, "⭐️"> // "Sem⭐️Lolo⭐️Kaquko"

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type JoinStrArray<
Arr extends string[],
Separator extends string,
Result extends string = ""
> =
Arr extends [infer El,...infer Rest]
? Rest extends string[]
? El extends string
? Result extends ""
? JoinStrArray<Rest, Separator,`${El}`>
: JoinStrArray<Rest, Separator,`${Result}${Separator}${El}`>
: `${Result}`
: `${Result}`
: `${Result}`

type Names = ["Sem", "Lolo", "Kaquko"]
type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"
type NamesSpace = JoinStrArray<Names, " "> // "Sem Lolo Kaquko"
type NamesStars = JoinStrArray<Names, "⭐️"> // "Sem⭐️Lolo⭐️Kaquko"

第十题

  • 实现一个Trim工具类型,用于对字符串字面量类型进行去空格处理,示例如下:
1
2
3
type Trim<V extends string> = // 代码实现

type Result = Trim<' foolishmax '>

解决方案

1
2
3
4
5
6
type TrimLeft<V extends string> = V extends ` ${infer R}` ? TrimLeft<R> : V;
type TrimRight<V extends string> = V extends `${infer R} `? TrimRight<R> : V;

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

type Result = Trim<' foolishmax '>

第十一题

  • 实现一个*IsEqual<A, B>*工具类型,用于比较两个类型是否相等,示例如下:
1
2
3
4
5
6
type IsEqual<A, B> = // 代码实现

// 测试用例
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{a : 1, {a: 1}}>; // true
type E2 = IsEqual<[1], []>; // false

解决方案(原始方案) 1

1
2
3
4
5
6
7
8
9
10
11
12
type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false;

// 测试用例
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{a : 1}, {a: 1}>; // true
type E2 = IsEqual<[1], []>; // false

// error
type E3 = IsEqual<true, boolean> // boolean
type E4 = IsEqual<1 | 2, 1> // boolean

这是因为泛型和*extends*两者结合所产生的*distributive conditionial types*效应导致的

解决方案(稍微优化)2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type IsEqual<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false

type IsEqual<A, B> = [A, B] extends [B, A] ? true : false

// 测试用例
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{a : 1}, {a: 1}>; // true
type E2 = IsEqual<[1], []>; // false

// error
type E3 = IsEqual<any, string> // true
type E4 = IsEqual< { name: string }, { readonly name: string }> // true

这是因为Tsany可以赋值为任何类型,任何类型也可以赋值给any,这就意味着any和任意类型之间都是assignable的,对于extends而言就是都可以相互extends的,所以E3true

readonly不会改变assignable。

解决方案 4

1
2
3
4
5
6
7
8
type IsEqual<A, B> =
(<G>() => G extends A ? 1 : 2) extends
(<G>() => G extends B ? 1 : 2) ? true : false;

// 测试用例
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{a : 1}, {a: 1}>; // true
type E2 = IsEqual<[1], []>; // false

第十二题

  • 实现一个Head工具类型,用于获取数组类型的第一个类型,示例如下:
1
2
3
4
5
6
type Head<T extends Array<any>> = // 代码实现

// 测试用例
type H0 = Head<[]> // never
type H1 = Head<[1]> // 1
type H2 = Head<[2,3,4]> // 2

解决方案 1

1
2
3
4
5
6
type Head<T extends Array<any>> = T extends [] ? never : T[0];

// 测试用例
type H0 = Head<[]> // never
type H1 = Head<[1]> // 1
type H2 = Head<[2,3,4]> // 2

解决方案 2

1
2
3
4
5
6
type Head<T extends Array<any>> = T extends [head: infer H, ...rest: any[]] ? H : never;

// 测试用例
type H0 = Head<[]> // never
type H1 = Head<[1]> // 1
type H2 = Head<[2,3,4]> // 2

第十三题

  • 实现一个Tail工具类型,用于获取数组类型除了第一个类型外,剩余的类型,示例如下:
1
2
3
4
5
6
type Tail<T extends Array<any>> = // 代码实现

// 测试用例
type T0 = Tail<[]> //[]
type T1 = Tail<[1, 2]> // [2]
type T2 = Tail<[1, 2, 3, 4, 5]> //[2, 3, 4, 5]

解决方案

1
2
3
4
5
6
type Tail<T extends Array<any>> = T extends [infer A, ...infer B] ? B : [];

// 测试用例
type T0 = Tail<[]> //[]
type T1 = Tail<[1, 2]> // [2]
type T2 = Tail<[1, 2, 3, 4, 5]> //[2, 3, 4, 5]

第十四题

  • 实现一个Unshift工具类型,用于把指定类型 E 作为第一个元素添加到T数组类型中,示例如下:
1
2
3
4
5
type Unshift<T extends any[], E> = // 代码实现

// 测试用例
type U0 = Unshift<[], 1>; // [1]
type U1 = Unshift<[1, 2, 3], 0>; //[0, 1, 2, 3]

解决方案

1
2
3
4
5
type Unshift<T extends any[], E> = [E, ...T];

// 测试用例
type U0 = Unshift<[], 1>; // [1]
type U1 = Unshift<[1, 2, 3], 0>; //[0, 1, 2, 3]

第十五题

  • 实现一个Shift工具类型,用于移除T数组类型中的第一个类型,示例如下:
1
2
3
4
5
type Shift<T extends any[]> = // 代码实现

// 测试用例
type S0 = Shift<[1, 2, 3]>
type S1 = Shift<[string, number, boolean]>

解决方案

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

// 测试用例
type S0 = Shift<[1, 2, 3]>; // [2, 3]
type S1 = Shift<[string, number, boolean]>; // [number, boolean]
type S2 = Shift<[]>; // []
type S3 = Shift<[string]>; // []

第十六题

  • 实现一个Push工具类型,用于把指定类型E作为最后一个元素添加到T数组类型中,示例如下:
1
2
3
4
5
type Push<T extends any[], E> = // 代码实现

// 测试用例
type P0 = Push<[], 1>; // [1]
type P1 = Push<[1, 2, 3], 4>; // [1, 2, 3, 4]

解决方案 1

1
2
3
4
5
type Push<T extends any[], E> = T extends [...infer U] ? [...U, E] : never;

// 测试用例
type P0 = Push<[], 1>; // [1]
type P1 = Push<[1, 2, 3], 4>; // [1, 2, 3, 4]

解决方案 2

1
2
3
4
5
6
type Push<T extends any[], E> = [...T, E];


// 测试用例
type P0 = Push<[], 1>; // [1]
type P1 = Push<[1, 2, 3], 4>; // [1, 2, 3, 4]

第十七题

  • 实现一个Includes工具类型,用于判断指定的类型E,是否包含在T数组类型中,示例如下:
1
2
3
4
5
type Includes<T extends any[], E> = // 代码实现

// 测试用例
type I0 = Includes<[], 1>; // false
type I1 = Includes<[2, 3], 2>; // true

解决方案 1

1
2
3
4
5
type Includes<T extends any[], E> = E extends T[number] ? true : false;

// 测试用例
type I0 = Includes<[], 1>; // false
type I1 = Includes<[2, 3], 2>; // true

解决方案 2

1
2
3
4
5
6
type Includes<T extends any[], E> = T extends [infer A, ...infer R] ? E extends A ? true : Includes<R, E> : false;


// 测试用例
type I0 = Includes<[], 1>; // false
type I1 = Includes<[2, 3], 2>; // true

第十八题

  • 实现一个UnionToIntersection工具类型,用于把联合类型转换为交叉类型,示例如下:
1
2
3
4
5
type UnionToIntersection<U> = // 代码实现

// 测试用例
type U0 = UnionToIntersection<string | number>; // never
type U1 = UnionToIntersection<{name: string} | {age: number}>; // {name: string} & {age: number}

解决方案

1
2
3
4
5
6
7
8
9
10
type Fun<X> = (...args: X[]) => void;

let f: Fun<string>
let g: Fun<string | number>

g = f // this cannot be assigned

当f赋值给g时,新的g不能使用number类型的参数,我们丢失了g的一部分类型,这就属于 逆变(contra-variance),这个和交集的工作机制类似。

当我们把逆变位置放在条件类型时:Typescript会创建一个交集,我们从函数参数中infer了一个类型,TypeScript知道我们必须符合逆变的条件,然后TypeScript会自动创建并集中所有的成分的交集。
1
2
3
4
5
6
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never

// 测试用例
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number }> // { name: string; } & { age: number; }

第十九题

  • 实现一个 OptionalKeys 工具类型,用来获取对象类型中声明的可选属性,示例如下:
1
2
3
4
5
6
7
8
9
10
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
}

type OptionalKeys<T> = // 代码实现
type PersonOptionsKeys = OptionalKeys<Person> // "from" | "speak"

解决方案 1

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
}

type OptionalKeys<T> = NonNullable<{
[P in keyof T]: undefined extends T[P] ? P : never
}[keyof T]>

type PersonOptionsKeys = OptionalKeys<Person> // "from" | "speak"

解决方案 2

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
}

type OptionalKeys<T> = keyof {
[P in keyof T as undefined extends T[P] ? P : never]: T[P]
}

type PersonOptionsKeys = OptionalKeys<Person> // "from" | "speak"

解决方案 3

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
};

type OptionalKeys<T> = Exclude<{
[P in keyof T]: T extends T[P] ? never : T[P]
}[keyof T], undefined>

type PersonOptionalKeys = OptionalKeys<Person>; // "from" | "speak

解决方案 4

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
};

type OptionalKeys<T> = {
[P in keyof T]: (undefined extends T[P] ? P : never)
}[keyof T] & keyof T

type PersonOptionalKeys = OptionalKeys<Person>; // "from" | "speak

第二十题

  • 实现一个Curry工具类型,用来实现函数类型的柯里化处理,示例如下:
1
2
3
4
5
6
7
8
9
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>,
R = ReturnType<F>
> = // 代码实现

type C0 = Curry<() => Date>; // () => Date
type C1 = Curry<(a:number) => Date>; // (arg: number) => Date
type C2 = Curry<(a: number, b: string) => Date>; // (arg: number)=>(arg: string) => Date

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>,
R = ReturnType<F>,
> = P extends [infer A, ...infer B]
? B extends []
? (arg: A) => R
: (arg: A) => Curry<(...arg: B) => R>
: F;

type C0 = Curry<() => Date>; // () => Date
type C1 = Curry<(a:number) => Date>; // (arg: number) => Date
type C2 = Curry<(a: number, b: string) => Date>; // (arg: number)=>(arg: string) => Date

第二十一题

  • 实现一个Merge工具类型,用于把两个类型合成一个新的类型,第二类型(SecondType)的Keys将会覆盖第一种类型(FirstType)的Keys,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
type Foo = {
a: number;
b: string;
}

type Bar = {
b: number;
}

type Merge<FirstType, SecondType> = // 代码实现

type M = Merge<Foo, Bar>; // {a: number, b: number}

解决方案 1

  • 将 FirstType 和 SecondType 做交叉类型,并遍历每一个属性;
  • 如果当前属性名在 SecondType 类型中,则使用 SecondType 类型中的当前属性值;
  • 如果当前属性名在 FirstType 类型中,则使用 FirstType 类型中的当前属性值;
  • 否则为 never;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Foo = {
a: number;
b: string;
}

type Bar = {
b: number;
}

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

type M = Merge<Foo, Bar>; // {a: number, b: number}

解决方案 2

  • 先将 FirstType 类型中已经有的,和 SecondType 类型中相同的属性删除;
  • 将前面结果和 SecondType 做交叉类型,获得合并后结果。
1
2
3
4
5
6
7
8
9
10
11
12
type Foo = {
a: number;
b: string;
}

type Bar = {
b: number;
}

type Merge<F, S> = Omit<F, keyof S> & S;

type M = Merge<Foo, Bar>; // {a: number, b: number}

第二十二题

  • 实现一个RequireAtLeastOne工具类型,它将创建一个至少含有一个给定keys的类型,其余keys类型保持原样,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
}

type RequireAtLeastOne<
ObjectType,
KeysType extends keyof ObjectType = keyof ObjectType,
> = 代码实现

const responder: RequireAtLeastOne<Responder, 'text'|'json'> = {
json: () => '{"message": "ok"}',
secure: true
}

解决方案 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
}

type RequireAtLeastOne<
ObjectType,
KeysType extends keyof ObjectType = keyof ObjectType,
> = KeysType extends unknown ? ObjectType & {
[K in KeysType]-?: ObjectType[K]
} : never;

// 表示当前类型至少包含 'text' 或 'json' 键
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
json: () => '{"message": "ok"}',
secure: true
};

// @ts-expect-error 因为没有'text'和'json'中的任何一个,报错
const responder2: RequireAtLeastOne<Responder, 'text' | 'json'> = {
secure: true
};

解决方案 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
};

type RequireAtLeastOne<ObjectType, KeysType extends keyof ObjectType = keyof ObjectType> = {
[K in keyof ObjectType]: K extends KeysType ? ObjectType & Required<Pick<ObjectType, K>> : never;
}[keyof ObjectType]

// 表示当前类型至少包含 'text' 或 'json' 键
const responder: RequireAtLeastOne<Responder, "text" | "json"> = {
json: () => '{"message": "ok"}',
secure: true,
};

解决方案 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
};

type RequireAtLeastOne<ObjectType, KeysType extends keyof ObjectType = KeysType extends unknown
? Omit<ObjectType, KeysType> & Require<Pick<ObjectType, KeysType>>
: never

// 表示当前类型至少包含 'text' 或 'json' 键
const responder: RequireAtLeastOne<Responder, "text" | "json"> = {
json: () => '{"message": "ok"}',
secure: true,
};

第二十三题

  • 实现一个RemoveIndexSignature工具类型,用于移除已有类型中的索引签名,示例如下:
1
2
3
4
5
6
7
8
9
10
interface Foo {
[key: string]: any;
[key: number]: any;
[key: symbol]: any;
bar(): void;
}

type RemoveIndexSignature<T> = // 代码实现

type R = RemoveIndexSignature<Foo>; // {bar: ()=>void;}

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
interface Foo {
[key: string]: any;
[key: number]: any;
[key: symbol]: any;
bar(): void;
}

type RemoveIndexSignature<T> = {
[K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K]
}

type R = RemoveIndexSignature<Foo>; // {bar: () => void;}

第二十四题

  • 实现一个 Mutable 工具类型,用于移除对象类型上所有属性或者部分属性的 readonly 修饰符,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
type Foo = {
readonly a: number;
readonly b: string;
readonly c: boolean;
}

type Mutable<T, Keys extends keyof T = keyof T> = // 代码实现

const mutableFoo: Mutable<Foo, 'a'> = { a: 1, b: '2', c: true };

mutableFoo.a = 3; // ok
mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.

解决方案 1

1
2
3
4
5
6
7
8
9
10
11
12
13
type Foo = {
readonly a: number;
readonly b: string;
readonly c: boolean;
}

type Mutable<T, Keys extends keyof T = keyof T> =
{-readonly [K in Keys]: T[K] } & Pick<T, Exclude<keyof T, Keys>>;

const mutableFoo: Mutable<Foo, 'a'> = { a: 1, b: '2', c: true };

mutableFoo.a = 3; // ok
mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.

解决方案 2

1
2
3
4
5
6
7
8
9
10
11
12
13
type Foo = {
readonly a: number;
readonly b: string;
readonly c: boolean;
}

type Mutable<T, Keys extends keyof T = keyof T> =
{-readonly [K in Keys]: T[K] : T[K]} & Omit<T, Keys>;

const mutableFoo: Mutable<Foo, 'a'> = { a: 1, b: '2', c: true };

mutableFoo.a = 3; // ok
mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.

第二十五题

  • 实现一个 IsUnion 工具类型,判断指定的类型是否为联合类型,示例如下:
1
2
3
4
5
type IsUnion<T, U = T> = // 代码实现

type I0 = IsUnion<string|number> // true
type I1 = IsUnion<string|never> // false
type I2 = IsUnion<string|unknown> //false

解决方案

1
2
3
4
5
type IsUnion<T, U = T> = T extends any ? [U] extends [T] ? false : true : never;

type I0 = IsUnion<string|number> // true
type I1 = IsUnion<string|never> // false
type I2 = IsUnion<string|unknown> //false

知识点: 1.联合类型作为泛型的时候 extends 会触发分发执行 2.联合类型 T 写成[T]就变成了普通类型,extends 的时候不会分发执行

这里第一步T extends any肯定为真,一个其实就是利用其分发的特性,后面的[T]就是一个联合类型拆开后的某一个,因此如果是联合类型的话[U] extends [T]一定为否

第二十六题

  • 实现一个IsNever工具类型,判断指定的类型是否为never类型,示例如下:
1
2
3
4
5
type IsNever<T> = // 代码实现

type I0 = IsNever<never> // true
type I1 = IsNever<never | string> // false
type I2 = IsNever<null> // false

解决方案

  • []包裹 T,否则泛型参数会被当作一个裸类型处理,走条件式分布类型的判断逻辑,当泛型参数是 any 这种特殊值时,会得到分布后的类型。
1
2
3
4
5
type IsNever<T> = [T] extends [never] ? true : false;

type I0 = IsNever<never> // true
type I1 = IsNever<never | string> // false
type I2 = IsNever<null> // false

第二十七题

  • 实现一个Reverse工具类型,用于对元祖类型中元素的位置颠倒,并返回该数组,元祖的第一个元素就会变成最后一个,最后一个元素变成第一个。
1
2
3
4
5
6
7
type Reverse<
T extends any[],
R extends any[] = []
> = // 代码实现

type R0 = Reverse<[]> // []
type R1 = Reverse<[1, 2, 3]> // [3, 2, 1]

解决方案 1

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

type R0 = Reverse<[]> // []
type R1 = Reverse<[1, 2, 3]> // [3, 2, 1]

解决方案 2

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

type R0 = Reverse<[]> // []
type R1 = Reverse<[1, 2, 3]> // [3, 2, 1]

第二十八题

  • 实现一个Split工具类型,根据给定的分割符(Delimiter)对包含分割符的字符串进行切割,可用于定义String.prototype.split方法的返回值类型,示例如下:
1
2
3
4
5
6
7
8
type Item = `zs, ls, ww`;

type Split<
S extends string,
Delimiter extends string
> = // 代码实现

type ElementType = Split<Item, ','>; // ["zs", "ls","ww"]

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Item = `zs, ls, ww`;

type Split<
S extends string,
Delimiter extends string,
> = S extends `${infer Key}${Delimiter}${infer Rest}`
? [Key, ...Split<Rest, Delimiter>]
: S extends '' /* 处理空字符串 */
? []
: [S]

type ElementType = Split<Item, ','>; // ["zs", "ls","ww"]
type ElementType2 = Split<'a|b|c||d', '|'>; // ["a", "b", "c", "", "d"]
type ElementType3 = Split<'abcdef', ''>; // ["a", "b", "c", "d", "e", "f"]

第二十九题

  • 实现一个ToPath工具类型,用于把属性访问(.或[])路径转换为元祖的形式,示例如下:
1
2
3
4
type ToPath<S extends string> = // 代码实现

ToPath<'foo.bar.baz'> // ['foo', 'bar', 'baz']
ToPath<'foo[0].bar.baz'> // ['foo', '0', 'bar', 'baz']

解决方案

1
2
3
4
5
6
7
8
9
10
type ToPath<S extends string> = S extends `${infer F}${`[${infer D}]`}${infer R}`
? [...ToPath<F>, ...([D] extends [never] ? [] : [D]), ...ToPath<R>]
: S extends `${infer F}.${infer R}`
? [...ToPath<F>, ...ToPath<R>]
: S extends ''
? []
: [S]

type T0 = ToPath<'foo.bar.baz'> // ['foo', 'bar', 'baz']
type T1 = ToPath<'foo[0].bar.baz'> // ['foo', '0', 'bar', 'baz']

第三十题

  • 完善Chainable类型的定义,是的 TS 能成功推断出result变量的类型,调用option方法之后会不断扩展当前对象的类型,使得调用get方法后能获取正确的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
declare const config: Chainable

type Chainable = {
option(key: string, value: any): any
get(): any
}

const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()

type ResultType = typeof result
// 期望 ResultType 的类型是:
// {
// age: number
// name: string
// address: {
// value: string
// }
// }

解决方案

1
2
3
4
5
6
7
8
9
10
declare const config: Chainable;

type Chainable<T = {}> = {
option<K extends string, V extends any>(key: K, value: V): Chainable<{ [P in K]: V } & T>;
get(): T;
};

const result = config.option("age", 7).option("name", "lolo").option("address", { value: "XiaMen" }).get();

type ResultType = typeof result;

第三十一题

  • 实现一个Repeat工具类型,用于根据类型变量C的值,重复T类型并以元祖的形式返回新的类型,示例如下:
1
2
3
4
5
type Repeat<T, C extends number> = // 代码实现

type R0 = Repeat<0, 0>; // []
type R1 = Repeat<1, 3>; // [1, 1, 1]
type R2 = Repeat<number, 2>; // [number, number]

解决方案

1
2
3
4
5
type Repeat<T, C extends number, A extends any[]> = A["length"] extends C ? A : Repeat<T, C, [...A, T]>;

type R0 = Repeat<0, 0>; // []
type R1 = Repeat<1, 3>; // [1, 1, 1]
type R2 = Repeat<number, 2>; // [number, number]

第三十二题

  • 实现一个RepeatString工具类型,用于根据类型变量C的值,重复 T 类型并以字符串的形式返回新的类型,示例如下:
1
2
3
4
5
6
7
type RepeatString<
T extends string,
C extends number,
> = // 代码实现

type S0 = RepeatString<"a", 0>; // ''
type S1 = RepeatString<"ab", 2>; // 'abab'

解决方案

1
2
3
4
5
6
7
8
9
10
11
type RepeatString<
T extends string,
C extends number,
S extends string = '',
A extends any[] = []
> = A["length"] extends C
? S
: RepeatString<T, C, `${S}${T}`, [...A, T]>

type S0 = RepeatString<"a", 0>; // ''
type S1 = RepeatString<"ab", 2>; // 'abab'

第三十三题

  • 实现一个ToNumber工具类型,用于实现把数值字符串转换为数值类型,示例如下:
1
2
3
4
5
type ToNumber<T extends string> = // 代码实现

type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

解决方案

1
2
3
4
5
6
type ToNumber<T extends string, S extends any[] = [], L extends number = S["length"]> =
`${L}` extends T ? L : ToNumber<T, [...S, 1]>

type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

第三十四题

  • 实现一个SmallerThan工具类型,用于比较数值类型的大小,示例如下:
1
2
3
4
5
6
7
8
type SmallerThan<
N extends number,
M extends number,
> = //代码实现

type S0 = SmallerThan<0, 1>; // true
type S1 = SmallerThan<2, 0>; // false
type S2 = SmallerThan<8, 10>; // true

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
type SmallerThan<
N extends number,
M extends number,
S extends any[] = [],
L extends number = S["length"]
> = L extends N
? L extends M ? false : true
: L extends M ? false : SmallerThan<N, M, [...S, 1]>

type S0 = SmallerThan<0, 1>; // true
type S1 = SmallerThan<2, 0>; // false
type S2 = SmallerThan<8, 10>; // true

第三十五题

  • 实现一个Add工具类型,用于实现对数组对应的数值进行加法运算,示例如下:
1
2
3
4
5
type Add<T, R> = // 代码实现

type A0 = Add<5, 5>; // 10
type A1 = Add<8, 20>; // 28
type A2 = Add<10, 20>; // 30

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
type Push<T extends number[], V> = [...T, V];

type CreateTuple<
T extends number,
A extends number[] = []
> = A["length"] extends T ? A : CreateTuple<T, Push<A, 1>>;

type Add<T extends number, R extends number> = [...CreateTuple<T>, ...CreateTuple<R>]["length"];

type A0 = Add<5, 5>; // 10
type A1 = Add<8, 20>; // 28
type A2 = Add<10, 20>; // 30

第三十六题

  • 实现一个Filter工具类型,用于根据类型变量F的值进行类型过滤,示例如下:
1
2
3
4
5
type Filter<T extends any[], F> = // 代码实现

type F0 = Filter<[6, "lolo", 7, "semlinker", false], number>; // [6, 7]
type F1 = Filter<["kakuqo", 2, ["ts"], "lolo"], string>; // ["kakuqo", "lolo"]
type F2 = Filter<[0, true, any, "abao"], string>; // [any, "abao"]

解决方案

1
2
3
4
// 本题关键点就是对`any`类型的处理:
type A0 = any & 1; // any
type A1 = any & boolean; // any
type A2 = any & never; // never
1
2
3
4
5
6
7
8
9
10
11
type IsAny<T> = 0 extends (1 & T) ? true : false;

type Filter<T extends any[], F> = T extends [infer R1, ...infer R2]
? IsAny<R1> extends true
? [R1, ...Filter<R2, F>]
: [...R1 extends F ? [R1] : [], ...Filter<R2, F>]
:[]

type F0 = Filter<[6, "lolo", 7, "semlinker", false], number>; // [6, 7]
type F1 = Filter<["kakuqo", 2, ["ts"], "lolo"], string>; // ["kakuqo", "lolo"]
type F2 = Filter<[0, true, any, "abao"], string>; // [any, "abao"]

第三十七题

  • 实现一个Flat工具类型,支持把数组类型拍平(扁平化),示例如下:
1
2
3
4
5
type Flat<T extends any[]> = // 代码实现

type F0 = Flat<[]>; // []
type F1 = Flat<['a', 'b', 'c']>; // ["a", "b", "c"]
type F2 = Flat<['a', ['b', 'c'], ['d', ['e', ['f']]]]>; // ["a", "b", "c", "d", "e", "f"]

解决方案 1

1
2
3
4
5
6
7
8
9
type Flat<T extends any[]> = T extends [infer First, ...infer Rest]
? First extends any[]
? [...Flat<First>, ...Flat<Rest>]
: [First, ...Flat<Rest>]
: [];

type F0 = Flat<[]>; // []
type F1 = Flat<['a', 'b', 'c']>; // ["a", "b", "c"]
type F2 = Flat<['a', ['b', 'c'], ['d', ['e', ['f']]]]>; // ["a", "b", "c", "d", "e", "f"]

解决方案 2

1
2
3
4
5
6
7
8
9
type Flat<T extends any[], S extends any[] = []> = T extends [infer R, ...infer Rest]
? R extends any[]
? Flat<Rest, Flat<R, S>>
: Flat<Rest, [...S, R]>
: S

type F0 = Flat<[]>; // []
type F1 = Flat<['a', 'b', 'c']>; // ["a", "b", "c"]
type F2 = Flat<['a', ['b', 'c'], ['d', ['e', ['f']]]]>; // ["a", "b", "c", "d", "e", "f"]

第三十八题

  • 实现StartsWith工具类型,判断字符串字面量类型T是否以给定的字符串字面量类型U开头,并根据判断结果返回布尔值,示例如下:
1
2
3
4
5
type StartsWith<T extends string, U extends string> = // 代码实现

type S0 = StartsWith<'123', '12'>; // true
type S1 = StartsWith<'123', '13'>; // false
type S2 = StartsWith<'123', '1234'>; // false
  • 之后,继续实现EndsWith工具类型,判断字符串字面量类型T是否以给定的字符串字面量类型U结尾,并根据判断结果返回布尔值,示例如下:
1
2
3
4
5
type EndsWith<T extends string, U extends string> = // 代码实现

type E0 = EndsWith<'123', '23'>; // true
type E1 = EndsWith<'123', '13'>; //false
type E2 = EndsWith<'123', '123'>; //true

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
// StartWith
type StartsWith<T extends string, U extends string> = T extends `${U}${infer R}` ? true : false;

type S0 = StartsWith<'123', '12'>; // true
type S1 = StartsWith<'123', '13'>; // false
type S2 = StartsWith<'123', '1234'>; // false

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

type E0 = EndsWith<'123', '23'>; // true
type E1 = EndsWith<'123', '13'>; //false
type E2 = EndsWith<'123', '123'>; //true

第三十九题

  • 实现IsAny工具类型,用于判断类型T是否为any类型,示例如下:
1
2
3
4
5
type IsAny<T> = // 代码实现

type I0 = IsAny<never>; // false
type I1 = IsAny<unknown>; // false
type I2 = IsAny<any>; // tue

解决方案 1

1
2
3
4
5
6
// 思路: 利用任何类型和any交叉都等于any来实现
type IsAny<T> = 0 extends 1 & T ? true : false;

type I0 = IsAny<never>; // false
type I1 = IsAny<unknown>; // false
type I2 = IsAny<any>; // tue

解决方案 2

1
2
3
4
5
6
// unknown 只能赋给 unknown 或者 any
type IsAny<T> = [unknown] extends [T] ? ([T] extends [string] ? true : false) : false;

type I0 = IsAny<never>; // false
type I1 = IsAny<unknown>; // false
type I2 = IsAny<any>; // tue

第四十题

  • 实现AnyOf工具类型,只要数组中任意元素的类型非Falsy类型、{}类型或[]类型,则返回true,否则返回false,如果数组为空的话,则返回false,示例如下:
1
2
3
4
5
type AnyOf<T extends any[]> = // 代码实现

type A0 = AnyOf<[]>; //false
type A1 = AnyOf<[0,'',false,[],{}]>; //false
type A2 = AnyOf<[1, "",false,[],{}]>; true

解决方案

1
2
3
4
5
6
7
type Falsy = {
[p in PropertyKey]: never
} | [] | '' | "" | false | 0 | undefined | null

type AnyOf<T extends any[]> = T extends [infer A, ...infer Rest]
? (A extends Falsy ? AnyOf<Rest> : true)
: false

第四十一题

  • 实现Replace工具类型,用于实现字符串类型的替换操作,具体的使用示例如下:
1
2
3
4
5
6
7
8
9
type Replace<
S extends string,
From extends string,
To extends string
> = // 代码实现

type R0 = Replace<'', '', ''>; // ''
type R1 = Replace<'foobar', 'bar', 'foo'>; // 'foofoo'
type R2 = Replace<'foobarbar', 'bar', 'foo'>; // 'foofoobar'
  • 此外,继续实现ReplaceAll工具类型,用于实现替换所有满足条件的字串,示例如下:
1
2
3
4
5
6
7
8
9
10
type ReplaceAll<
S extends string,
From extends string,
To extends string
> = // 代码实现

type R0 = ReplaceAll<'', '', ''>; // ''
type R1 = ReplaceAll<'barfoo', 'bar', 'foo'>; // 'foofoo'
type R2 = ReplaceAll<'foobarbar', 'bar', 'foo'>; // 'foofoofoo'
type R3 = ReplaceAll<'foobarfoobar', 'ob', 'b'>; // 'fobarfobar'

解决方案

1
2
3
4
5
6
7
8
9
10
11
type Replace<
S extends string,
From extends string,
To extends string,
> = S extends `${infer H}${From}${infer R}`
? `${H}${To}${R}`
: S;

type R0 = Replace<'', '', ''>; // ''
type R1 = Replace<'foobar', 'bar', 'foo'>; // 'foofoo'
type R2 = Replace<'foobarbar', 'bar', 'foo'>; // 'foofoobar'
1
2
3
4
5
6
7
8
9
10
11
12
type ReplaceAll<
S extends string,
From extends string,
To extends string,
> = S extends `${infer H}${From}${infer R}`
? `${H}${To}${ReplaceAll<R, From, To>}`
: S;

type R0 = ReplaceAll<'', '', ''>; // ''
type R1 = ReplaceAll<'barfoo', 'bar', 'foo'>; // 'foofoo'
type R2 = ReplaceAll<'foobarbar', 'bar', 'foo'>; // 'foofoofoo'
type R3 = ReplaceAll<'foobarfoobar', 'ob', 'b'>; // 'fobarfobar'

第四十二题

  • 实现IndexOf工具类型,用于获取数组类型中指定项的索引值,若不存在的话,则返回-1字面量类型,示例如下:
1
2
3
4
5
6
type IndexOf<A extends any[], Item> = // 代码实现

type Arr = [1, 2, 3, 4, 5];
type I0 = IndexOf<Arr, 0>; // -1
type I1 = IndexOf<Arr, 1>; // 0
type I2 = IndexOf<Arr, 3>; // 2

解决方案

1
2
3
4
5
6
7
8
9
10
type IndexOf<A extends any[], Item, R extends any[] = []> = A extends [infer H, ...infer Rest]
? Item extends H
? R["length"]
: IndexOf<Rest, Item, [...R, H]>
: -1

type Arr = [1, 2, 3, 4, 5];
type I0 = IndexOf<Arr, 0>; // -1
type I1 = IndexOf<Arr, 1>; // 0
type I2 = IndexOf<Arr, 3>; // 2

第四十三题

  • 实现一个Permutation工具类型,当输入一个联合类型时,返回一个包含该联合类型的全排列类型数组。示例如下:
1
2
3
4
type Permutation<T, K = T> = // 代码实现

type P0 = Permutation<'a' | 'b'>; // ['a', 'b'] | ['b' , 'a']
type P1 = Permutation<'a' | 'b' | 'c'>; //["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] | ["b", "c", "a"] | ["c", "a", "b"] | ["c", "b", "a"]

解决方案

1
2
3
4
5
6
7
8
type Permutation<T, K = T> = [T] extends [never]
? []
: K extends K
? [K, ...Permutation<Exclude<T, K>>]
: never

type P0 = Permutation<'a' | 'b'>; // ['a', 'b'] | ['b' , 'a']
type P1 = Permutation<'a' | 'b' | 'c'>; //["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] | ["b", "c", "a"] | ["c", "a", "b"] | ["c", "b", "a"]

第四十四题

  • 实现Unpacked工具类型,用于对类型执行“拆箱”操作,示例如下:
1
2
3
4
5
6
7
8
9
10
type Unpacked<T> = // 代码实现

// 测试用例
type T00 = Unpacked<string>; // string
type T01 = Unpacked<string[]>; // string
type T02 = Unpacked<() => string>; // string
type T03 = Unpacked<Promise<string>>; // string
type T04 = Unpacked<Unpacked<Promise<string>[]>>; // string
type T05 = Unpacked<any>; // any
type T06 = Unpacked<never>; // never

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Unpacked<T> = T extends (...args: any) => infer A
? A
: T extends Promise<infer B>
? B
: T extends (infer A)[]
? A
: T

// 测试用例
type T00 = Unpacked<string>; // string
type T01 = Unpacked<string[]>; // string
type T02 = Unpacked<() => string>; // string
type T03 = Unpacked<Promise<string>>; // string
type T04 = Unpacked<Unpacked<Promise<string>[]>>; // string
type T05 = Unpacked<any>; // any
type T06 = Unpacked<never>; // never

第四十五题

  • 实现JsonifiedObject工具类型,用于对Object对象类型进行序列话操作,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
declare class MyClass {
toJSON(): "MyClass";
}
type Jsonified<T extends object> = // 代码实现

type MyObject = {
str: "literalstring";
fn: () => void;
date: Date;
customClass: MyClass;
obj: {
prop: "property";
clz: MyClass;
nested: { attr: Date };
};
};

type JsonifiedMyObject = Jsonified<MyObject>;
declare let ex: JsonifiedMyObject;
const z1: "MyClass" = ex.customClass;
const z2: string = ex.obj.nested.attr;

解决方案

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
declare class MyClass {
toJSON(): "MyClass";
}
type Jsonified<T extends object> = {
[K in keyof T]: T[K] extends { toJSON(): infer Return }
? ReturnType<T[K]["toJSON"]>
: T[K] extends (...arg: any[]) => any
? never
: T[K] extends object
? Jsonified<T[K]>
: T[K];
};

type MyObject = {
str: "literalstring";
fn: () => void;
date: Date;
customClass: MyClass;
obj: {
prop: "property";
clz: MyClass;
nested: { attr: Date };
};
};

type JsonifiedMyObject = Jsonified<MyObject>;
declare let ex: JsonifiedMyObject;
const z1: "MyClass" = ex.customClass;
const z2: string = ex.obj.nested.attr;

第四十六题

  • 实现RequireAllOrNone工具类型,用于满足以下功能,当设置age属性时,gender属性也会变成必填,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
name: string;
age?: number;
gender?: number;
}

type RequireAllOrNone<T, K extends keyof T> = // 你的实现代码

const p1: RequireAllOrNone<Person, 'age' | 'gender'> = {
name: "lolo"
};

const p2: RequireAllOrNone<Person, 'age' | 'gender'> = {
name: "lolo",
age: 7,
gender: 1
};

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Person {
name: string;
age?: number;
gender?: number;
}

type RequireAllOrNone<T, K extends keyof T> = Omit<T, K> & (
{[P in K]-?: T[P]} | {[P in K]?: never}
)

const p1: RequireAllOrNone<Person, 'age' | 'gender'> = {
name: "lolo"
};

const p2: RequireAllOrNone<Person, 'age' | 'gender'> = {
name: "lolo",
age: 7,
gender: 1
};

const p3: RequireAllOrNone<Person, 'age' | 'gender'> = { // error
name: "lolo",
age: 7,
};

第四十七题

  • 实现RequireExactlyOne工具类型,用于满足以下功能,即只能包含agegender属性,不能包含着两个属性,示例如下:
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
interface Person {
name: string;
age?: number;
gender?: number;
}

// 只能包含Keys中唯一的一个Key
type RequireExactlyOne<T, Keys extends keyof T> = // 你的实现代码

const p1: RequireExactlyOne<Person, 'age' | 'gender'> = {
name: "lolo",
age: 7,
};

const p2: RequireExactlyOne<Person, 'age' | 'gender'> = {
name: "lolo",
gender: 1
};

// Error
const p3: RequireExactlyOne<Person, 'age' | 'gender'> = {
name: "lolo",
age: 7,
gender: 1
};

解决方案

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
interface Person {
name: string;
age?: number;
gender?: number;
}

// 只能包含Keys中唯一的一个Key
type RequireExactlyOne<T, Keys extends keyof T, K extends keyof T = Keys> =
Keys extends any
? Omit<T, K> & Required<Pick<T, Keys>> & Partial<Record<Exclude<K, Keys>, never>>
: never;

const p1: RequireExactlyOne<Person, 'age' | 'gender'> = {
name: "lolo",
age: 7,
};

const p2: RequireExactlyOne<Person, 'age' | 'gender'> = {
name: "lolo",
gender: 1
};

// Error
const p3: RequireExactlyOne<Person, 'age' | 'gender'> = {
name: "lolo",
age: 7,
gender: 1
};

第四十八题

  • 实现ConsistsOnlyOf工具类型,用于判断LongString字符串类型是否由 0 个或多个Substring字符串类型组成,示例如下:
1
2
3
4
5
6
type ConsistsOnlyOf<LongString extends string, SubString extends string> = // 代码实现

type C0 = ConsistsOnlyOf<'aaa', 'a'>; // true
type C1 = ConsistsOnlyOf<'ababab', 'ab'>; // true
type C2 = ConsistsOnlyOf<'aBa', 'a'>; // false
type C3 = ConsistsOnlyOf<'', 'a'>; // true

解决方案

1
2
3
4
5
6
7
8
9
10
11
type ConsistsOnlyOf<LongString extends string, SubString extends string> =
LongString extends ''
? true
: LongString extends `${SubString}${infer R}`
? ConsistsOnlyOf<R, SubString>
: false

type C0 = ConsistsOnlyOf<'aaa', 'a'>; // true
type C1 = ConsistsOnlyOf<'ababab', 'ab'>; // true
type C2 = ConsistsOnlyOf<'aBa', 'a'>; // false
type C3 = ConsistsOnlyOf<'', 'a'>; // false

第四十九题

  • 项目中定义了接口返回的数据的类型,每层都能灵活扩展一些属性,怎么做呢?
1
2
3
4
5
6
7
// 接口返回数据结构
type Data = {
aaa?: number;
bbb: {
ccc: number;
};
};

解决方案

1
2
3
4
5
6
7
type DeepRecord<Obj extends Record<keyof any, unknown>> = {
[key in keyof Obj]: Obj[key] extends Record<keyof any, unknown>
? DeepRecord<Obj[key]> & Record<keyof any, unknown>
: Obj[key];
} & Record<keyof any, unknown>;

type IData = DeepRecord<Data>;

第五十题

  • 当一个索引为 ‘desc’ | ‘asc’ 的时候,其他索引都是 false

解决方案

1
2
3
4
5
6
7
8
9
type GenerateType<keys extends keyof any, V1, V2> = {
[key in keys]: {
[key1 in key]: V1;
} & {
[key2 in Exclude<keys, key>]: V2;
};
}[keys];

type R = GenerateType<"a" | "b" | "c" | "d", "desc" | "asc", false>;

第五十一题

  • 取出interfaceuserInfo的类型
1
2
3
4
5
6
7
interface Result {
data?: {
userInfo?: {
name: string;
};
};
}

解决方案 1

1
2
3
4
// 简单版 Required
// 缺点 层级深了需要写多个Required

type UserInfo = Required<Required<Result>["data"]>["userInfo"];

解决方案 2

1
2
3
4
5
6
7
8
9
10
11
// 递归Required
// 缺点数据类型都会变成Required
type IsOptional<Key extends keyof Obj, Obj> = {} extends Pick<Obj, Key>
? Key
: never;

type DeepRequired<T> = {
[K in keyof T]-?: IsOptional<K, T> extends never ? T[K] : DeepRequired<T[K]>;
};

type UserInfo = DeepRequired<Result>["data"]["userInfo"];

评论