TypeScript - 映射类型:初学者指南

你好啊,未来的 TypeScript 巫师们!今天,我们将踏上一段激动人心的旅程,探索映射类型的奥秘。如果你是编程新手,不用担心——我会作为你的友好向导,一步一步地带你学习。在本教程结束时,你将能够像专业人士一样使用映射类型!

TypeScript - Mapped Types

什么是映射类型?

在我们深入之前,先来了解一下映射类型是什么。想象你有一盒各种巧克力的混合盒,你想要创建一个新盒子,里面每块巧克力都包上金色箔纸。这在 TypeScript 中 essentially 就是映射类型的作用——它们根据一组规则,将一个现有的类型转换成一个新的类型。

内置映射类型

TypeScript 提供了一些预定义的映射类型,非常实用。让我们一个一个来看:

1. Partial<T>

Partial<T> 类型使 T 的所有属性变为可选。这就好比将一个严格的食谱变成一个灵活的食谱,你可以跳过一些食材。

interface Recipe {
name: string;
ingredients: string[];
cookingTime: number;
}

type FlexibleRecipe = Partial<Recipe>;

// 现在我们可以在不指定所有属性的情况下创建一个食谱
const quickSnack: FlexibleRecipe = {
name: "Toast",
// 我们可以跳过 ingredients 和 cookingTime
};

在这个例子中,FlexibleRecipe 允许我们在不指定所有属性的情况下创建一个食谱。当你要更新对象的一部分时,这非常完美。

2. Required<T>

Required<T>Partial<T> 的相反。它使得所有属性都是必需的,即使它们在原始类型中是可选的。

interface UserProfile {
name: string;
age?: number;
email?: string;
}

type CompleteUserProfile = Required<UserProfile>;

// 现在我们必须提供所有属性
const user: CompleteUserProfile = {
name: "Alice",
age: 30,
email: "[email protected]"
};

在这里,CompleteUserProfile 确保我们提供所有属性,包括原始 UserProfile 中可选的 ageemail

3. Readonly<T>

Readonly<T> 使得 T 的所有属性都变为只读。这就好比把你的类型放在一个玻璃盒子里——你可以看,但不能碰!

interface Toy {
name: string;
price: number;
}

type CollectibleToy = Readonly<Toy>;

const actionFigure: CollectibleToy = {
name: "Superhero",
price: 19.99
};

// 这将导致错误
// actionFigure.price = 29.99;

在这个例子中,一旦我们创建了 actionFigure,就不能修改它的属性。这对于创建不可变对象非常棒。

4. Pick<T, K>

Pick<T, K> 通过选择 T 中的指定属性 K 来创建一个新的类型。这就好比从类型中选择你喜欢的特性。

interface Smartphone {
brand: string;
model: string;
year: number;
color: string;
price: number;
}

type BasicPhoneInfo = Pick<Smartphone, 'brand' | 'model'>;

const myPhone: BasicPhoneInfo = {
brand: "TechBrand",
model: "X2000"
};

在这里,BasicPhoneInfo 只包括 Smartphonebrandmodel 属性,而忽略了其他属性。

5. Omit<T, K>

Omit<T, K>Pick<T, K> 的相反。它通过移除 T 中的指定属性 K 来创建一个新的类型。

interface Book {
title: string;
author: string;
pages: number;
isbn: string;
}

type BookPreview = Omit<Book, 'pages' | 'isbn'>;

const preview: BookPreview = {
title: "TypeScript 冒险",
author: "代码巫师"
};

在这个例子中,BookPreview 包括 Book 的所有属性,除了 pagesisbn

映射类型的示例

现在我们已经看到了内置的映射类型,让我们看看一些实际应用中的例子。

示例 1:创建表单状态

想象你正在构建一个表单,并且你想要跟踪哪些字段已经被修改:

interface LoginForm {
username: string;
password: string;
rememberMe: boolean;
}

type FormTouched = { [K in keyof LoginForm]: boolean };

const touchedFields: FormTouched = {
username: true,
password: false,
rememberMe: true
};

在这里,我们创建了一个 FormTouched 类型,它有与 LoginForm 相同的键,但所有值都是布尔值,表示字段是否被触摸。

示例 2:API 响应包装器

假设你有一个返回不同类型数据的 API,你想要将每个响应包装在一个标准格式中:

interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
timestamp: number;
}

type UserData = { id: number; name: string; email: string };
type ProductData = { id: number; name: string; price: number };

const userResponse: ApiResponse<UserData> = {
data: { id: 1, name: "John Doe", email: "[email protected]" },
status: 'success',
timestamp: Date.now()
};

const productResponse: ApiResponse<ProductData> = {
data: { id: 101, name: "Laptop", price: 999.99 },
status: 'success',
timestamp: Date.now()
};

这个例子展示了我们如何使用泛型和映射类型来创建灵活、可重用的类型结构。

创建自定义映射类型

现在,让我们展示一下我们的 TypeScript 技能,并创建一些自定义映射类型!

自定义类型 1:Nullable

让我们创建一个使所有属性都可为空的类型:

type Nullable<T> = { [K in keyof T]: T[K] | null };

interface Person {
name: string;
age: number;
}

type NullablePerson = Nullable<Person>;

const maybePerson: NullablePerson = {
name: "Jane",
age: null  // 现在这有效
};

我们的 Nullable<T> 类型允许任何属性是其原始类型或 null

自定义类型 2:Freezable

让我们创建一个向对象添加 freeze 方法的类型:

type Freezable<T> = T & { freeze(): Readonly<T> };

interface Config {
theme: string;
fontSize: number;
}

function makeFreezable<T>(obj: T): Freezable<T> {
return {
...obj,
freeze() {
return Object.freeze({ ...this }) as Readonly<T>;
}
};
}

const config = makeFreezable<Config>({
theme: "dark",
fontSize: 14
});

const frozenConfig = config.freeze();
// frozenConfig.theme = "light";  // 这将导致错误

这个自定义类型向任何对象添加了一个 freeze 方法,允许我们创建它的不可变版本。

结论

哇,我们今天涵盖了大量的内容!从内置的映射类型到创建我们自己的自定义类型,你已经看到了 TypeScript 可以有多么强大和灵活。映射类型就像是你的 TypeScript 工具箱中的魔杖——它们允许你以非常实用的方式转换和操作类型。

记住,掌握映射类型的关键是实践。尝试创建你自己的类型,尝试不同的组合,很快你写的 TypeScript 代码不仅功能强大,而且优雅且类型安全。

继续编码,继续学习,最重要的是,用 TypeScript 享受乐趣!

映射类型 描述 示例
Partial<T> 使所有属性可选 type PartialUser = Partial<User>
Required<T> 使所有属性必需 type RequiredUser = Required<User>
Readonly<T> 使所有属性只读 type ReadonlyUser = Readonly<User>
Pick<T, K> 创建一个只包含指定属性的类型 type UserName = Pick<User, 'name'>
Omit<T, K> 创建一个不包含指定属性的类型 type UserWithoutPassword = Omit<User, 'password'>
Record<K, T> 创建一个对象类型,键为 K,值为 T type StringMap = Record<string, string>
Exclude<T, U> 从 T 中排除 U 中的类型 type NumberOnly = Exclude<number | string, string>
Extract<T, U> 从 T 中提取 U 中的类型 type StringOnly = Extract<number | string, string>
NonNullable<T> 从 T 中移除 null 和 undefined type NonNullableString = NonNullable<string | null | undefined>
Parameters<T> 提取函数类型的参数类型 type Params = Parameters<(a: number, b: string) => void>
ReturnType<T> 提取函数类型的返回类型 type ReturnString = ReturnType<() => string>
InstanceType<T> 提取构造函数类型的实例类型 type DateInstance = InstanceType<typeof Date>

Credits: Image by storyset