TypeScript 映射类型详解及应用(二十一)
- 前端开发
- 5天前
- 6热度
- 0评论
在现代前端开发中,TypeScript 以其强大的类型系统逐渐成为主流选择。映射类型(Mapped Types)作为 TypeScript 类型系统中的一个重要特性,能够帮助开发者高效地创建和管理复杂类型。本文将详细介绍映射类型的基本概念、常见用法以及实际应用场景,帮助你在项目中更好地利用这一强大工具。
什么是映射类型?
映射类型是一种基于现有类型创建新类型的机制。通过遍历已有类型的所有属性,并对每个属性应用相同的类型转换规则,我们可以快速生成新的类型。这种声明式的类型生成方式不仅减少了手动定义类型的工作量,还提高了代码的可维护性和灵活性。
为什么需要映射类型?
在实际开发中,我们经常需要基于现有的类型创建一些变体。例如,将所有属性变为可选的类型、将所有属性变为只读的类型等。传统的做法是手动定义这些类型,但这不仅繁琐且容易出错。映射类型提供了一种简洁而强大的方式来自动化这一过程。
基础映射类型
基础映射类型通过遍历原始类型的所有属性来创建新的类型。这是实现 Partial、Readonly 等内置工具类型的基础。
示例
假设我们有一个 User 接口:
interface User {
id: number;
name: string;
}我们可以使用映射类型将其所有属性变为可选:
type PartialUser = {
[P in keyof User]?: User[P];
};结果类型为:
type PartialUser = {
id?: number;
name?: string;
};语法解释
- [P in keyof T]:遍历 T 类型的所有键。
- ?:将属性设为可选。
属性修饰符
映射类型支持多种属性修饰符,这些修饰符可以组合使用,实现不同的类型转换需求。
可选修饰符
通过在属性名前添加 ? 修饰符,可以将属性设为可选:
type Partial
<T> = {
[P in keyof T]?: T[P];
};只读修饰符
通过在属性名前添加 readonly 修饰符,可以将属性设为只读:
type Readonly
<T> = {
readonly [P in keyof T]: T[P];
};移除可选修饰符
通过在属性名前添加 -? 修饰符,可以移除原有的可选修饰符:
type Required
<T> = {
[P in keyof T]-?: T[P];
};示例
interface User {
name: string;
age: number;
}
const readonlyUser: Readonly
<User> = {
name: "Alice",
age: 25,
};
// readonlyUser.age = 30; // 错误:只读属性不能修改
const optionalUser: Partial
<User> = {
name: "Bob",
};
console.log("只读: " + JSON.stringify(readonlyUser));
console.log("可选: " + JSON.stringify(optionalUser));键名映射
映射类型还可以使用 as 关键字来重映射键名。这在需要统一修改键名格式时非常有用。
示例
假设我们有一个 User 接口:
interface User {
id: number;
name: string;
age: number;
}我们可以使用 as 关键字为所有键添加前缀:
type WithPrefix<T, Prefix extends string> = {
[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];
};
type PrefixedUser = WithPrefix<User, "user">;
const user: PrefixedUser = {
userId: 1,
userName: "Alice",
userAge: 25,
};
console.log("带前缀: " + JSON.stringify(user));模板字面量类型
使用 ${Prefix}${Capitalize} 可以动态生成新的键名。Capitalize 用于将字符串的首字母大写。
键过滤
通过条件类型和映射类型的组合,可以实现键的过滤。这在实现 Omit 等工具类型时非常有用。
示例
假设我们有一个 User 接口:
interface User {
id: number;
name: string;
password: string;
email: string;
}我们可以使用条件类型过滤掉指定的键:
type Omit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
type UserWithoutPassword = Omit<User, "password">;
const user: UserWithoutPassword = {
id: 1,
name: "Alice",
email: "a@b.com",
};
console.log("无密码: " + JSON.stringify(user));never 类型
在映射类型中使用 never 作为属性类型,该属性会被完全移除。
条件映射
映射类型可以与条件类型结合,根据属性类型的不同应用不同的转换。这使得类型转换更加灵活和智能。
示例
假设我们有一个 APIResponse 接口:
interface APIResponse {
data: string;
error: string;
isLoading: boolean;
timestamp: number;
}我们可以将函数类型转换为 () => void:
type FunctionToVoid
<T> = {
[P in keyof T]: T[P] extends (...args: any[]) => any ? () => void : T[P];
};
const response: FunctionToVoid
<APIResponse> = {
data: "hello",
error: "",
isLoading: false,
timestamp: Date.now(),
};
console.log("响应: " + JSON.stringify(response));应用场景
条件映射常用于处理 API 响应、清理配置对象等需要根据类型做不同处理的场景。
内置映射类型
TypeScript 内置了许多基于映射类型实现的工具类型,这些工具类型可以满足大多数日常开发需求。
示例
// Partial - 将所有属性变为可选
type P1 = Partial<{ a: string; b: number }>; // { a?: string; b?: number }
// Required - 将所有可选属性变为必填
type R1 = Required<{ a?: string; b?: number }>; // { a: string; b: number }
// Readonly - 将所有属性变为只读
type RO1 = Readonly<{ a: string; b: number }>; // { readonly a: string; readonly b: number }
// Pick - 选择指定的属性
type PK = Pick<{ a: string; b: number; c: boolean }, "a" | "b">; // { a: string; b: number }
// Omit - 排除指定的属性
type OM = Omit<{ a: string; b: number; c: boolean }, "c">; // { a: string; b: number }
console.log("Partial: " + JSON.stringify({} as P1));
console.log("Pick: " + JSON.stringify({ a: "x" } as PK));工具类型组合
这些内置工具类型都是基于映射类型和条件类型实现的。了解其原理可以更好地使用它们。
注意事项
- keyof 关键字: 用于获取类型的所有键组成的联合类型。
- in 关键字: 用于遍历键名联合类型。
- 修饰符位置: ? 和 readonly 在属性名前,表示添加修饰符。
- 减号修饰符: -? 和 -readonly 用于移除修饰符。
- as 关键字: 用于重映射键名,必须返回字符串或数字字面量类型。
进阶
映射类型可以与条件类型、模板字面量类型组合,实现复杂的类型转换。
总结
映射类型是 TypeScript 类型系统中最强大的特性之一。通过 keyof 获取类型的所有键,使用 in 遍历键名进行映射,添加 ? 和 readonly 修饰符,使用 as 重映射键名,映射类型提供了丰富的功能来简化类型定义和管理。
- keyof: 获取类型的所有键
- in: 遍历键名进行映射
- ?: 添加可选修饰符
- readonly: 添加只读修饰符
- -?: 移除可选修饰符
- as: 重映射键名
善用映射类型可以大幅减少重复的类型定义,提高代码的可维护性。希望本文能帮助你在 TypeScript 开发中更好地利用这一强大工具。