TypeScript 映射类型与条件类型(附形象记忆法)

TypeScript 映射类型与条件类型(附形象记忆法)

一、映射类型(Mapped Types)详解

1.1 基础概念

映射类型允许我们基于现有类型的属性,创建一个新类型,通常用于批量修改属性。

核心语法

1
2
3
type MappedType<T> = {
[P in keyof T]: /* 对属性P的操作 */
};

1.2 常见内置映射类型

1.2.1 Readonly<T>

将所有属性设为只读:

1
2
3
4
5
6
7
8
9
10
11
interface User {
name: string;
age: number;
}

type ReadonlyUser = Readonly<User>;
// 等同于:
// {
// readonly name: string;
// readonly age: number;
// }

形象记忆:就像给所有门加上”禁止进入”的锁(readonly

1.2.2 Partial<T>

将所有属性变为可选:

1
2
3
4
5
6
type PartialUser = Partial<User>;
// 等同于:
// {
// name?: string;
// age?: number;
// }

形象记忆:就像把所有必填字段变成”选填”(?

1.2.3 Required<T>

将所有属性变为必填(与 Partial 相反)

1
2
type RequiredUser = Required<PartialUser>;
// 恢复所有属性为必填

1.2.4 Record<K, T>

创建一个对象类型,其键为 K,值为 T

1
2
type UserRoles = Record<string, "admin" | "user" | "guest">;
// 等同于 { [key: string]: "admin" | "user" | "guest" }

形象记忆:就像创建一个”键值对”的表格

1.2.5 Pick<T, K>

从 T 中选取指定的属性 K

1
2
type UserName = Pick<User, "name">;
// 等同于 { name: string }

形象记忆:就像从菜单中”点菜”(只选需要的)

1.2.6 Omit<T, K>

从 T 中排除指定的属性 K

1
2
type UserWithoutAge = Omit<User, "age">;
// 等同于 { name: string }

形象记忆:就像从套餐中”删除”不想要的项


1.3 自定义映射类型

1.3.1 添加前缀/后缀

1
2
3
4
5
6
type AddPrefix<T, Prefix extends string> = {
[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];
};

type PrefixedUser = AddPrefix<User, "user_">;
// 等同于 { user_name: string; user_age: number }

形象记忆:就像给所有文件名加前缀

1.3.2 条件修改属性

1
2
3
4
5
6
type MakeNullable<T> = {
[P in keyof T]: T[P] | null;
};

type NullableUser = MakeNullable<User>;
// 等同于 { name: string | null; age: number | null }

形象记忆:就像给所有必填字段加上”可为空”的选项


二、条件类型(Conditional Types)详解

2.1 基础语法

1
T extends U ? X : Y

如果类型 T 可以赋值给 U,则结果为 X,否则为 Y

2.2 常见用法

2.2.1 类型判断

1
2
3
4
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

形象记忆:就像做选择题(是/否)

2.2.2 提取数组元素类型

1
2
3
4
5
type ElementType<T> = T extends (infer U)[] ? U : never;

type Str = ElementType<string[]>; // string
type Num = ElementType<number[]>; // number
type NeverArr = ElementType<number>; // never

形象记忆:就像从容器中”提取”内容

2.2.3 分支处理

1
2
3
4
5
6
7
8
9
type ResponseType<T> = T extends { error: any }
? "error"
: T extends { data: any }
? "success"
: "unknown";

type Success = ResponseType<{ data: string }>; // "success"
type Error = ResponseType<{ error: string }>; // "error"
type Unknown = ResponseType<{ status: number }>; // "unknown"

形象记忆:就像交通信号灯(不同条件走不同分支)


2.3 高级用法

2.3.1 分布式条件类型

当 T 是联合类型时,条件类型会自动分发:

1
2
3
4
type ToArray<T> = T extends any ? T[] : never;

type StrOrNumArray = ToArray<string | number>;
// 等同于 string[] | number[]

形象记忆:就像批量处理邮件(每个收件人收到单独的邮件)

2.3.2 条件类型递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type DeepReadonly<T> = {
[P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

interface Nested {
a: number;
b: {
c: string;
d: {
e: boolean;
};
};
}

type ReadonlyNested = DeepReadonly<Nested>;
// 所有层级都变为readonly

形象记忆:就像俄罗斯套娃,一层层深入


三、形象记忆法总结

概念 记忆技巧
Readonly<T> 给所有属性加”禁止修改”标签
Partial<T> 把所有必填变成”选填”
Pick<T,K> 从菜单中”点菜”
Omit<T,K> 从套餐中”删除”不想要的
AddPrefix<T,P> 给文件名加前缀
MakeNullable<T> 给必填字段加”可为空”选项
IsString<T> 做选择题(是/否)
ElementType<T> 从容器中”提取”内容
分布式条件类型 批量处理邮件(每个收件人单独处理)
条件类型递归 俄罗斯套娃,一层层深入