D21. 高级类型操作
1.1. 🌟 Partial 和 Required:可选性魔法转换器
1.1.1. Partial:让属性全部可选
核心思想: Partial 是 TypeScript 的内置工具类型,可将对象类型的所有属性变为 可选属性(?
)。它适合在 部分更新 或 可选参数传递 的场景中使用。
语法与示例
// 定义原始类型
interface User {
name: string;
age: number;
bio: string;
}
// 使用 Partial 将所有属性变为可选
type PartialUser = Partial<User>;
// 应用场景:部分更新用户信息
function updateUser(user: Partial<User>): void {
console.log(user);
}
// 调用时可只传递部分属性
updateUser({ name: "Alice" }); // 合法
updateUser({ age: 30, bio: "Developer" }); // 合法
实现原理
// Partial 的源码实现
type Partial<T> = {
[P in keyof T]?: T[P]; // 遍历 T 的所有属性,添加 ?
};
TIP
Partial 的核心是 映射类型(Mapped Types),通过 keyof T
获取所有属性,再通过 ?
标记为可选。
WARNING
如果原始类型中有 ?
可选属性,Partial 会保留其可选性。例如:
interface User {
name: string;
age?: number; // 原始属性已可选
}
type PartialUser = Partial<User>; // age 仍为可选
1.1.2. Required:让属性全部必填
核心思想: Required 是 Partial 的逆操作,将对象类型的所有属性变为 必填属性(移除 ?
)。它适合在 强制校验数据完整性 的场景中使用。
语法与示例
// 定义原始类型
interface User {
name: string;
age?: number; // 原始属性可选
}
// 使用 Required 将所有属性变为必填
type RequiredUser = Required<User>;
// 应用场景:确保接口返回的数据完整
const fetchUser = async (): Promise<Required<User>> => {
// 必须返回包含 name 和 age 的对象
return { name: "Bob", age: 25 };
};
实现原理
// Required 的源码实现
type Required<T> = {
[P in keyof T]-?: T[P]; // 遍历 T 的所有属性,强制必填(-?)
};
TIP
-?
是 TypeScript 的 必需性修饰符,用于强制移除可选属性的 ?
。
WARNING
如果原始类型中某个属性本身是 必填的,Required 会保留其必填性。
1.1.3. 实战案例:表单更新功能
场景:设计一个用户表单,允许部分更新信息(如仅修改 name
或 bio
)。
interface User {
name: string;
age: number;
bio: string;
}
// 使用 Partial 实现部分更新
function updateProfile(profile: Partial<User>): void {
console.log("更新的属性:", profile);
}
// 调用示例
updateProfile({ name: "Charlie" }); // 合法
updateProfile({ bio: "Designer" }); // 合法
1.2. 🌟 Pick 和 Omit:精准裁剪对象属性
1.2.1. Pick:选择性保留属性
核心思想: Pick 可从原始类型中 选择指定属性,生成新类型。它适合在 提取核心字段 或 简化接口 的场景中使用。
语法与示例
interface User {
id: number;
name: string;
email: string;
age: number;
address: string;
}
// 只保留 name 和 email
type UserInfo = Pick<User, "name" | "email">;
const userCard: UserInfo = {
name: "Alice",
email: "alice@example.com",
}; // 合法,address 等属性不可用
实现原理
// Pick 的源码实现
type Pick<T, K extends keyof T> = {
[P in K]: T[P]; // 仅保留 K 中的属性
};
TIP
K 必须是原始类型 T 的属性子集,否则会报错。
1.2.2. Omit:选择性剔除属性
核心思想: Omit 可从原始类型中 剔除指定属性,生成新类型。它适合在 排除敏感字段 或 简化数据传输 的场景中使用。
语法与示例
interface User {
id: number;
name: string;
password: string; // 敏感字段
email: string;
}
// 排除 password 和 id
type PublicUser = Omit<User, "password" | "id">;
const publicProfile: PublicUser = {
name: "Bob",
email: "bob@example.com",
}; // 合法,password 和 id 不可用
实现原理
// Omit 的源码实现
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
TIP
Omit 的底层实现依赖 Pick 和 Exclude,通过排除目标属性后选择剩余属性。
1.2.3. 实战案例:用户资料编辑场景
场景:设计用户资料编辑页,允许用户编辑部分信息(如仅编辑 name
和 email
)。
interface User {
id: number;
name: string;
email: string;
password: string;
}
// 使用 Pick 提取可编辑字段
type EditableUser = Pick<User, "name" | "email">;
function editProfile(profile: EditableUser): void {
console.log("编辑的字段:", profile);
}
// 调用示例
editProfile({ name: "Charlie", email: "charlie@example.com" }); // 合法
1.3. 🌟 typeof 和 interface:从实例到类型
1.3.1. typeof 的基本用法
核心思想: typeof
可直接获取变量的类型,适合从 具体实例推导类型,避免手动重复定义。
语法与示例
// 定义实例对象
const config = {
host: "localhost",
port: 3000,
timeout: 5000,
};
// 通过 typeof 获取类型
type ConfigType = typeof config;
// 使用类型
const newConfig: ConfigType = { ...config }; // 合法
TIP
typeof
的类型会精确匹配实例的结构,包括属性类型和可选性。
1.3.2. typeof 与 interface 的结合
核心思想: 结合 typeof
和 interface
,可灵活扩展或修改推导的类型。
语法与示例
const baseConfig = {
host: "localhost",
port: 3000,
};
type BaseConfigType = typeof baseConfig;
interface ExtendedConfig extends BaseConfigType {
timeout: number; // 扩展新属性
}
const extendedConfig: ExtendedConfig = {
host: "localhost",
port: 3000,
timeout: 5000,
};
WARNING
如果实例被修改,typeof
获取的类型不会自动更新!例如:
let config = { name: "Alice" };
type ConfigType = typeof config; // { name: string }
config = { age: 30 }; // ❌ 类型错误!类型不匹配
1.3.3. 坑位警示:对象修改后的类型陷阱
问题: typeof
的类型是静态的,若实例对象动态修改,可能导致类型错误。
解决方案: 使用 接口(interface) 或 类型别名(type) 定义类型,避免依赖 typeof
的动态性。
// 使用接口替代 typeof
interface Config {
host: string;
port: number;
}
let config: Config = { host: "localhost", port: 3000 };
config = { host: "remote", port: 8080 }; // 合法
知识回顾
- Partial 和 Required
Partial<T>
:让所有属性可选。Required<T>
:让所有属性必填。
- Pick 和 Omit
Pick<T, K>
:保留指定属性。Omit<T, K>
:剔除指定属性。
- typeof
- 通过实例推导类型,但需注意动态修改的陷阱。
课后练习
(单选)以下哪个工具类型可以将
User
类型的age
属性变为可选? A.Pick<User>
B.Partial<User>
C.Omit<User>
D.Required<User>
(填空)使用
Pick
提取User
类型的name
和email
属性,代码为:type UserInfo = Pick<User, ___>;
(编码)定义一个
Config
接口,包含host
(必填)、timeout
(可选)。使用typeof
获取以下对象的类型,并确保类型正确:typescriptconst config = { host: "localhost", timeout: 5000 };