TypeScript - 映射類型:初學者指南
你好,未來的 TypeScript 巫師們!今天,我們將踏上一段令人興奮的旅程,進入映射類型的世界。別擔心你對編程還是新手——我會成為你的友好導遊,我們會一步一步地學習。在本教程結束時,你將能夠像專業人士一樣映射類型!
映射類型是什麼?
在我們深入之前,讓我們先了解映射類型是什麼。想像你有一盒各種巧克力,你想創造一個新盒子,裡面的每個巧克力都包著金色紙。這基本上就是 TypeScript 中的映射類型所做的——它們取一個現有的類型,並根據一套規則將其轉換為新的類型。
內置的映射類型
TypeScript 提供了一些預定義的映射類型,非常實用。讓我們一個一個地看看:
1. Partial<T>
Partial<T>
類型使 T
的所有屬性變為可選。這就像將嚴格的菜譜變為靈活的菜譜,你可以跳過一些材料。
interface Recipe {
name: string;
ingredients: string[];
cookingTime: number;
}
type FlexibleRecipe = Partial<Recipe>;
// 現在我們可以創建一個不包含所有屬性的菜譜
const quickSnack: FlexibleRecipe = {
name: "烤麵包",
// 我們可以跳過材料和方法時間
};
在這個例子中,FlexibleRecipe
允許我們創建一個不指定所有屬性的菜譜。當你只想更新對象的一部分時,這非常適合。
2. Required<T>
Required<T>
是 Partial<T>
的相反。它使所有屬性都成為必填,即使它們在原始類型中是可選的。
interface UserProfile {
name: string;
age?: number;
email?: string;
}
type CompleteUserProfile = Required<UserProfile>;
// 現在我們必須提供所有屬性
const user: CompleteUserProfile = {
name: "艾麗絲",
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: "超級英雄",
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: "科技品牌",
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: "約翰·多伊", email: "[email protected]" },
status: 'success',
timestamp: Date.now()
};
const productResponse: ApiResponse<ProductData> = {
data: { id: 101, name: "筆記本電腦", price: 999.99 },
status: 'success',
timestamp: Date.now()
};
這個例子展示了如何使用泛型與映射類型來創建灵活、可重用的類型結構。
創建自定義映射類型
現在,讓我們展現一下 TypeScript 的實力,創建一些自定義映射類型!
自定義類型 1:Nullable
讓我們創建一個類型,使所有屬性可為 null:
type Nullable<T> = { [K in keyof T]: T[K] | null };
interface Person {
name: string;
age: number;
}
type NullablePerson = Nullable<Person>;
const maybePerson: NullablePerson = {
name: "簡",
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: "深色",
fontSize: 14
});
const frozenConfig = config.freeze();
// frozenConfig.theme = "亮色"; // 這會導致錯誤
這個自定義類型為任何對象添加了一個 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