TypeScript - 映射类型:初学者指南
你好啊,未来的 TypeScript 巫师们!今天,我们将踏上一段激动人心的旅程,探索映射类型的奥秘。如果你是编程新手,不用担心——我会作为你的友好向导,一步一步地带你学习。在本教程结束时,你将能够像专业人士一样使用映射类型!
什么是映射类型?
在我们深入之前,先来了解一下映射类型是什么。想象你有一盒各种巧克力的混合盒,你想要创建一个新盒子,里面每块巧克力都包上金色箔纸。这在 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
中可选的 age
和 email
。
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
只包括 Smartphone
的 brand
和 model
属性,而忽略了其他属性。
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
的所有属性,除了 pages
和 isbn
。
映射类型的示例
现在我们已经看到了内置的映射类型,让我们看看一些实际应用中的例子。
示例 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