TypeScript 练习题

实现 Pick
实现 Readonly
元组转换为对象
第一个元素
实现 Exclude
Promise 返回值类型
实现 Array.Concat
实现 Array.includes
实现 Parameters
实现 ReturnType
实现 Omit
Pick Readonly
Deep Readonly
链式调用的类型
Promise.all
Type Lookup
Trim
Type Replace
追加参数
Flatten
AppendToObject
数字转字符串
StringToUnion
MergeKey
CamelCase & KebabCase
Diff
anyOf
isUnion

实现 Pick
1
2
3
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

利用 keyof 将对象类型转换成键值的联合类型

利用 extends 进行泛型约束, K 可以分配给 T, 表示 K 是 T 的子集.

利用 in 运算符,遍历联合类型

实现 Readonly
1
2
3
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
元组转换为对象
1
2
3
4
5
6
7
8
const tuple = ["tesla", "model 3", "model X", "model Y"] as const;

type TupleToObject<T extends readonly any[]> = {
[K in T[number]]: K;
};

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

因为 in 运算符可以遍历联合类型,所以把元组 T 转换成联合类型,在进行遍历

第一个元素

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

1
2
3
4
5
6
7
8
9
type arr2 = [3, 2, 1];

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

type First<T extends readonly any[]> = T extends [infer F, ...infer R]
? F
: never;

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

利用条件语句中 infer 类型推断,返回第一个元素所代表的类型

实现 Exclude

Exclude 的用法是从联合类型中,排除指定的属性

1
type Exclude<T, U> = T extends U ? never : T;

extends 条件类型, T 是否能分配给 U, 会去拿 T 中的每一项与 U 进行匹配, 如果当前项可以分配,表示 U 中存在这种类型,需要排除,所以返回 never. 如果不存在则返回这一项的类型.

Promise 返回值类型
1
type Awaited<T extends Promise<any>> = T extends Promise<infer R> ? R : T;
实现 Array.Concat
1
type Concat<T extends any[], U extends any[]> = [...T, ...U];
实现 Array.includes
1
type Includes<T extends any[], U> = U extends T[number] ? true : false;

利用 extends 条件类型可以进行联合类型的判断,, 首先吧元组转换为联合类型, 如果类型可分配表示 U 存在与元组中.

实现 Parameters

Parameters 作用是用于获得函数的参数类型组成的元组类型。

1
2
3
4
5
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;
实现 ReturnType
1
2
3
4
5
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
实现 Omit
1
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Pick Readonly

指定属性 ReadOnly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type PickReadonly<T, K extends keyof T = keyof T> = {
[Key in Exclude<keyof T, K>]: T[Key];
} & {
readonly [Key in K]: T[Key];
};

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
Deep Readonly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type X = {
x: {
a: 1;
b: "hi";
};
y: "hey";
};

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

type Todo = DeepReadonly<X>; // should be same as `Expected`

type Expected = {
readonly x: {
readonly a: 1;
readonly b: "hi";
};
readonly y: "hey";
};
链式调用的类型

假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Chainable<T = {}> = {
option: <K extends string, V>(k: K, v: V) => Chainable<T & { [P in K]: V }>;
get: () => T;
};

declare const config: Chainable;

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

// 期望 result 的类型是:
interface Result {
foo: number;
name: string;
bar: {
value: string;
};
}

实现 Promise.all

ts 允许像遍历一个对象一样遍历类数组

1
2
3
4
5
6
7
8
9
10
11
12
13
type Awaited<T> =
T extends null | undefined ? T :
// special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F): any } ?
// `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
F extends ((value: infer V, ...args: any) => any) ? // if the argument to `then` is callable, extracts the first argument
Awaited<V> :
// recursively unwrap the value
never :
// the argument to `then` was not callable
T;
// non-object or non-thenable
type PromiseAll<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;

Type Lookup

1
2
3
type LookUp<U, T extends string> = {
[K in T]: U extends { type: T } ? U : never;
}[T];

实现 Trim

类型推断可以用于字符串

1
2
3
4
5
type Trim<T extends string> = T extends ` ${infer R}`
? Trim<R>
: T extends `${infer R} `
? Trim<R>
: T;

Type Replace

ts 没有 indexOf 的能力, 通过条件类型判断两个类型是否匹配

1
2
3
4
5
type Replace<
T extends string,
P extends string,
U extends string
> = T extends `${infer F}${P}${infer R}` ? `${F}${U}${R}` : T;

追加参数

1
2
3
4
5
type AppendArgument<F extends (...args: any[]) => any, P> = F extends (
...args: infer R
) => infer L
? (...args: [...R, P]) => L
: F;

Flatten

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

AppendToObject

对象 Key 的类型约束,有两种方式

1
2
3
4
5
6
// K extends PropertyKey 其中 PropertyKey 为内置属性
// K extends keyof any

type AppendToObject<T, K extends PropertyKey, V> = {
[Key in keyof T | K]: Key extends keyof T ? T[Key] : V;
};

另一种是现实是重新遍历一次组合后的对象

1
2
3
4
5
6
7
type MapKey<T> = { [K in keyof T]: T[K] };

type AppendToObject<T, K extends keyof any, V> = MapKey<
T & {
[K1 in K]: V;
}
>;

数子转字符串

1
type Test<T extends number> = `${T}`;

可以把非字符串类型转为字符串,利用类型系统处理

1
2
3
4
5
type Test<T extends number | string | bigint> = `${T}` extends `-${infer R}`
? R
: T;

type dd = Test<"-123">; //123

StringToUnion

1
2
3
4
5
6
7
8
9
10
11
type StringToUnion<
T extends string,
A extends string[] = []
> = T extends `${infer F}${infer R}` ? StringToUnion<R, [...A, F]> : A[number];

type StringToUnion<
T extends string,
A = never
> = T extends `${infer F}${infer R}` ? StringToUnion<R, A | F> : A;

type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"

MergeKey

1
2
3
4
5
6
7
8
9
10
11
12
13
type Merge<T, P extends { [k in PropertyKey]: any }> = {
[K in keyof foo | keyof coo]: (foo & coo)[K] extends never
? P[K]
: (foo & coo)[K];
};

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

CamelCase & KebabCase

aa-bb-cc => aaBbCc

1
2
3
4
5
6
7
type CamelCase<T extends string> = T extends `${infer F}-${infer D}${infer R}`
? CamelCase<`${F}${Uppercase<D>}${R}`>
: T;

type CamelCase<T extends string> = T extends `${infer F}-${infer D}`
? CamelCase<`${F}${Capitalize<D>}`>
: T;

AaBbCc => aa-bb-cc

1
2
3
4
5
6
7
8
9
10
type KebabCase<
T extends string,
P extends string = ""
> = T extends `${infer F}${infer R}`
? Lowercase<F> extends F
? KebabCase<R, `${P}${F}`>
: KebabCase<R, `${P}-${Lowercase<F>}`>
: P extends `-${infer R}`
? R
: never;

Diff

1
2
3
4
5
6
7
8
9
type Diff<T extends object, P extends object> = {
[K in
| Exclude<keyof T, keyof P>
| Exclude<keyof P, keyof T>]: K extends keyof T
? T[K]
: K extends keyof P
? P[K]
: never;
};

anyOf

实现一个类型,接受一个元组,如果元组中的每一个都为 false,返回 false,有一个为 true 则返回 true

需要注意联合类型是通过 T[number] 得到的,并不会进行条件类型分配,所以当联合类型可以分配给指定联合类型, 也就是联合类型中的每一个都在指定的联合类型中的时候返回 false

1
2
3
4
5
6
7
8
type AnyOf<T extends any[]> = T[number] extends
| ""
| 0
| false
| []
| Record<any, never>
? false
: true;

isUnion

利用 分配条件类型,联合类型在条件分配后会被转换为复合联合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type IsUnion<T, O = T> = T extends O ? ([O] extends [T] ? false : true) : never;

type case1 = IsUnion<string>;
type case2 = IsUnion<string | number>; // true
type case3 = IsUnion<[string | number]>; // false

// 利用分配条件类型把基本类型的联合类型转换为符合类型的联合类型

type Test<T, O = T> = T extends O ? [T] : never;

type case1 = Test<string>; //[string]
type case2 = Test<string | number>; // [string] | [number]
type case3 = Test<[string | number]>; // [[string|number]]

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信