Skip to content

D21. 高级类型操作

1.1. 🌟 Partial 和 Required:可选性魔法转换器

1.1.1. Partial:让属性全部可选

核心思想: Partial 是 TypeScript 的内置工具类型,可将对象类型的所有属性变为 可选属性?)。它适合在 部分更新可选参数传递 的场景中使用。

语法与示例

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" }); // 合法

实现原理

typescript
// Partial 的源码实现
type Partial<T> = {
  [P in keyof T]?: T[P]; // 遍历 T 的所有属性,添加 ?
};

TIP

Partial 的核心是 映射类型(Mapped Types),通过 keyof T 获取所有属性,再通过 ? 标记为可选。

WARNING

如果原始类型中有 ? 可选属性,Partial 会保留其可选性。例如:

typescript
interface User {
  name: string;
  age?: number; // 原始属性已可选
}
type PartialUser = Partial<User>; // age 仍为可选

1.1.2. Required:让属性全部必填

核心思想: Required 是 Partial 的逆操作,将对象类型的所有属性变为 必填属性(移除 ?)。它适合在 强制校验数据完整性 的场景中使用。

语法与示例

typescript
// 定义原始类型
interface User {
  name: string;
  age?: number; // 原始属性可选
}

// 使用 Required 将所有属性变为必填
type RequiredUser = Required<User>;

// 应用场景:确保接口返回的数据完整
const fetchUser = async (): Promise<Required<User>> => {
  // 必须返回包含 name 和 age 的对象
  return { name: "Bob", age: 25 };
};

实现原理

typescript
// Required 的源码实现
type Required<T> = {
  [P in keyof T]-?: T[P]; // 遍历 T 的所有属性,强制必填(-?)
};

TIP

-? 是 TypeScript 的 必需性修饰符,用于强制移除可选属性的 ?

WARNING

如果原始类型中某个属性本身是 必填的,Required 会保留其必填性。

1.1.3. 实战案例:表单更新功能

场景:设计一个用户表单,允许部分更新信息(如仅修改 namebio)。

typescript
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 可从原始类型中 选择指定属性,生成新类型。它适合在 提取核心字段简化接口 的场景中使用。

语法与示例

typescript
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 等属性不可用

实现原理

typescript
// Pick 的源码实现
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]; // 仅保留 K 中的属性
};

TIP

K 必须是原始类型 T 的属性子集,否则会报错。

1.2.2. Omit:选择性剔除属性

核心思想: Omit 可从原始类型中 剔除指定属性,生成新类型。它适合在 排除敏感字段简化数据传输 的场景中使用。

语法与示例

typescript
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 不可用

实现原理

typescript
// Omit 的源码实现
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

TIP

Omit 的底层实现依赖 Pick 和 Exclude,通过排除目标属性后选择剩余属性。

1.2.3. 实战案例:用户资料编辑场景

场景:设计用户资料编辑页,允许用户编辑部分信息(如仅编辑 nameemail)。

typescript
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 可直接获取变量的类型,适合从 具体实例推导类型,避免手动重复定义。

语法与示例

typescript
// 定义实例对象
const config = {
  host: "localhost",
  port: 3000,
  timeout: 5000,
};

// 通过 typeof 获取类型
type ConfigType = typeof config;

// 使用类型
const newConfig: ConfigType = { ...config }; // 合法

TIP

typeof 的类型会精确匹配实例的结构,包括属性类型和可选性。

1.3.2. typeof 与 interface 的结合

核心思想: 结合 typeofinterface,可灵活扩展或修改推导的类型。

语法与示例

typescript
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 获取的类型不会自动更新!例如:

typescript
let config = { name: "Alice" };
type ConfigType = typeof config; // { name: string }

config = { age: 30 }; // ❌ 类型错误!类型不匹配

1.3.3. 坑位警示:对象修改后的类型陷阱

问题typeof 的类型是静态的,若实例对象动态修改,可能导致类型错误。

解决方案: 使用 接口(interface)类型别名(type) 定义类型,避免依赖 typeof 的动态性。

typescript
// 使用接口替代 typeof
interface Config {
  host: string;
  port: number;
}

let config: Config = { host: "localhost", port: 3000 };
config = { host: "remote", port: 8080 }; // 合法

知识回顾

  1. Partial 和 Required
    • Partial<T>:让所有属性可选。
    • Required<T>:让所有属性必填。
  2. Pick 和 Omit
    • Pick<T, K>:保留指定属性。
    • Omit<T, K>:剔除指定属性。
  3. typeof
    • 通过实例推导类型,但需注意动态修改的陷阱。

课后练习

  1. (单选)以下哪个工具类型可以将 User 类型的 age 属性变为可选? A. Pick<User> B. Partial<User> C. Omit<User> D. Required<User>

  2. (填空)使用 Pick 提取 User 类型的 nameemail 属性,代码为: type UserInfo = Pick<User, ___>;

  3. (编码)定义一个 Config 接口,包含 host(必填)、timeout(可选)。使用 typeof 获取以下对象的类型,并确保类型正确:

    typescript
    const config = { host: "localhost", timeout: 5000 };

扩展阅读

Built by Vitepress | Apache 2.0 Licensed