映射類型(Mapped Types)
什麼是映射類型 (Mapped Types)
如果今天有一個類型全部都是一樣的 (例:string) 並且 key 非常非常的多,每一筆都寫 string 就會顯的又臭又長
type TInfo = {
firstname: string,
lastname: string,
sex: string,
username: string,
zip: string,
address: string,
//...要寫到什麼時候…
}
而我們改用映射類型寫:
使用映射
type TInfoMapped = { [K in TInfoKeys]: string };
就可以解決寫一大串的型別
另外,也可以用 Record:
使用 Record
type TInfoKeys = 'firstname' | 'lastname' | 'sex' | 'username' | 'zip' | 'address';
type TInfoValue = Record<TInfoKeys, string>;
基本語法
type Mapped<T> = {
[K in keyof T]: T[K];
};
關鍵點:
- keyof: 取得型別
T的所有鍵組合(聯集) - in: 跑迴圈
- T[K]: 取出
T在鍵K上的屬性型別
常用內建映射類型
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends PropertyKey, V> = { [P in K]: V };
修飾子加減號
// ?(可選)
type WithOptional<T> = { [K in keyof T]?: T[K] };
// 必填
type WithoutOptional<T> = { [K in keyof T]-?: T[K] };
// 加上 readonly
type WithReadonly<T> = { readonly [K in keyof T]: T[K] };
// 移除 readonly
type WithoutReadonly<T> = { -readonly [K in keyof T]: T[K] };
鍵重映射(Key Remapping)與樣板字面值
TS 4.1+ 支援用 as 重新命名鍵,並可搭配樣板字面值:
type PrefixKeys<T extends Record<string, any>> = {
[K in keyof T as `app_${Extract<K, string>}`]: T[K]
};
type Original = { id: number; name: string };
type Prefixed = PrefixKeys<Original>;
// => { app_id: number; app_name: string }
PrefixKeys<T extends Record<string, any>>- 這裡使用
extends約束了泛型必須要為string: any的物件類型
app_${Extract<K, string>}- 這裡用了樣板字面值搭配
as和extract將 key 值重新命名 Extract:聯合類型選取指定內容,組成新的 type,這裡選取了泛型K並且指定為一定是string類型
來練習
練習一:把所有屬性改成可選/唯讀
// 實作 ToOptional 與 ToReadonly
type ToOptional<T> = unknown;
type ToReadonly<T> = unknown;
type User = {
id: string;
name: string;
active: boolean;
};
type U1 = ToOptional<User>;
// 期望:{ id?: string; name?: string; active?: boolean }
type U2 = ToReadonly<User>;
// 期望:{ readonly id: string; readonly name: string; readonly active: boolean }
點擊查看解答
type ToOptional<T> = { [K in keyof T]?: T[K] };
type ToReadonly<T> = { readonly [K in keyof T]: T[K] };
type User = {
id: string;
name: string;
active: boolean;
};
type U1 = ToOptional<User>;
type U2 = ToReadonly<User>;
練習二:移除修飾並統一必填
// 實作 MutableRequired:移除 readonly 與 ?,全部改成必填且可變
type MutableRequired<T> = unknown;
type Post = {
readonly id: string;
title?: string;
readonly published?: boolean;
};
type P1 = MutableRequired<Post>;
// 期望:{ id: string; title: string; published: boolean }
點擊查看解答
type MutableRequired<T> = { -readonly [K in keyof T]-?: T[K] };
type Post = {
readonly id: string;
title?: string;
readonly published?: boolean;
};
type P1 = MutableRequired<Post>;
練習三:鍵重映射 + 條件轉型
// 實作 ApiShape:
// - 把鍵加上前綴 'api_'
// - 若值型別可指派為 Date,改成 string,否則維持原型別
type ApiShape<T> = unknown;
type Entity = {
id: number;
name: string;
createdAt: Date;
};
type E1 = ApiShape<Entity>;
/* 期望:
{
api_id: number;
api_name: string;
api_createdAt: string;
}
*/
點擊查看解答
type ApiShape<T extends Record<string, any>> = { [K in keyof T as `api_${Extract<K, string>}`]: T[K] extends Data ? string : T[K] };
type Entity = {
id: number;
name: string;
createdAt: Date;
};
type E1 = ApiShape<Entity>;