TypeScript 映射類型與索引類型
映射類型可以將一個類型轉換成另一個類型。本篇將介紹這個強大的類型轉換技巧。
一、 索引存取類型
1.1 基本用法
typescript
interface User {
id: number;
name: string;
email: string;
}
// 取得單個屬性類型
type UserId = User["id"]; // number
type UserName = User["name"]; // string1.2 聯合索引
typescript
interface User {
id: number;
name: string;
email: string;
}
// 取得多個屬性的聯合類型
type UserIdOrName = User["id" | "name"]; // number | string1.3 取得所有值類型
typescript
interface User {
id: number;
name: string;
isAdmin: boolean;
}
type UserValues = User[keyof User]; // number | string | boolean1.4 陣列元素類型
typescript
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" },
];
type User = (typeof users)[number];
// { id: number, name: string }二、 映射類型基礎
2.1 基本語法
typescript
// 語法:{ [K in Keys]: Type }
type Keys = "a" | "b" | "c";
type MyObject = {
[K in Keys]: string;
};
// { a: string, b: string, c: string }2.2 搭配 keyof
typescript
interface User {
id: number;
name: string;
email: string;
}
// 將所有屬性變成 boolean
type UserFlags = {
[K in keyof User]: boolean;
};
// { id: boolean, name: boolean, email: boolean }2.3 保留原始類型
typescript
interface User {
id: number;
name: string;
}
// 映射但保留原類型
type UserCopy = {
[K in keyof User]: User[K];
};
// { id: number, name: string }三、 修飾符
3.1 readonly 修飾符
typescript
interface User {
id: number;
name: string;
}
// 添加 readonly
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// { readonly id: number, readonly name: string }
// 移除 readonly(使用 -)
type MutableUser = {
-readonly [K in keyof User]: User[K];
};3.2 可選修飾符
typescript
interface User {
id: number;
name: string;
}
// 添加可選
type PartialUser = {
[K in keyof User]?: User[K];
};
// { id?: number, name?: string }
// 移除可選
type RequiredUser = {
[K in keyof User]-?: User[K];
};四、 內建工具類型
4.1 Partial<T>
typescript
// 將所有屬性變成可選
type Partial<T> = {
[K in keyof T]?: T[K];
};
interface User {
id: number;
name: string;
}
type PartialUser = Partial<User>;
// { id?: number, name?: string }4.2 Required<T>
typescript
// 將所有屬性變成必填
type Required<T> = {
[K in keyof T]-?: T[K];
};
interface User {
id?: number;
name?: string;
}
type RequiredUser = Required<User>;
// { id: number, name: string }4.3 Readonly<T>
typescript
// 將所有屬性變成唯讀
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};4.4 Record<K, V>
typescript
// 建立物件類型
type Record<K extends keyof any, V> = {
[P in K]: V;
};
type Roles = "admin" | "user" | "guest";
type RolePermissions = Record<Roles, boolean>;
// { admin: boolean, user: boolean, guest: boolean }4.5 Pick<T, K>
typescript
// 選取特定屬性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
id: number;
name: string;
email: string;
}
type UserBasic = Pick<User, "id" | "name">;
// { id: number, name: string }4.6 Omit<T, K>
typescript
// 排除特定屬性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
interface User {
id: number;
name: string;
password: string;
}
type PublicUser = Omit<User, "password">;
// { id: number, name: string }五、 鍵重映射
5.1 as 子句
TypeScript 4.1+ 支援使用 as 重映射鍵:
typescript
interface User {
id: number;
name: string;
}
// 添加前綴
type GetterUser = {
[K in keyof User as `get${Capitalize<string & K>}`]: () => User[K];
};
// { getId: () => number, getName: () => string }5.2 過濾鍵
typescript
interface User {
id: number;
name: string;
isAdmin: boolean;
}
// 只保留 string 類型的屬性
type StringKeysOnly<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type StringProps = StringKeysOnly<User>;
// { name: string }5.3 移除特定屬性
typescript
// 移除 id 屬性
type RemoveId<T> = {
[K in keyof T as Exclude<K, "id">]: T[K];
};
interface User {
id: number;
name: string;
}
type UserWithoutId = RemoveId<User>;
// { name: string }六、 實用範例
6.1 Getters
typescript
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string, getAge: () => number }6.2 事件處理器
typescript
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void;
};
interface Form {
username: string;
password: string;
}
type FormHandlers = EventHandlers<Form>;
// { onUsernameChange: (value: string) => void, onPasswordChange: (value: string) => void }6.3 深度 Readonly
typescript
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface Config {
api: {
url: string;
timeout: number;
};
}
type ReadonlyConfig = DeepReadonly<Config>;
// 所有層級都是 readonly總結
| 語法 | 說明 | 範例 |
|---|---|---|
T[K] | 索引存取 | User['name'] |
T[keyof T] | 所有值類型 | 聯合類型 |
{ [K in Keys]: T } | 映射類型 | 遍歷鍵 |
readonly | 唯讀修飾符 | 不可變 |
? / -? | 可選修飾符 | 添加/移除 |
as | 鍵重映射 | 轉換鍵名 |
**應用場景**:
- 表單 state 和 errors 對應
- API 請求和回應轉換
- 設定物件處理
進階挑戰
- 實作
DeepPartial<T>:深度可選 - 實作
Mutable<T>:移除所有 readonly - 實作一個
Path<T>類型,生成物件的所有路徑
typescript
// 練習 3 提示
interface User {
name: string;
address: { city: string };
}
type P = Path<User>; // 'name' | 'address' | 'address.city'