本文會和大家詳細介紹 TypeScript 中的對映型別(Mapped Type),看完本文你將學到以下知識點:
- 數學中的對映和 TS 中的對映型別的關係;
- TS 中對映型別的應用;
- TS 中對映型別修飾符的應用;
接下來會先從「數學中的對映」開始介紹。
本文使用到的 TypeScript 版本為 v4.6.2。
如果你對 TypeScript 還不熟悉,可以看下面幾篇資料:
一、什麼是對映?
在學習 TypeScript 型別系統時,儘量多和數學中的集合類比學習,比如 TypeScript 中的聯合型別,類似數學中的並集等。
在數學中,對映是指兩個元素的集合之間元素相互對應的關係,比如下圖:
(來源:https://baike.baidu.com/item/%E6%98%A0%E5%B0%84/20402621)
可以將對映理解為函式,如上圖,當我們需要將集合 A 的元素轉換為集合 B 的元素,可以通過 f
函式做對映,比如將集合 A 的元素 1
對應到集合 B 中的元素 2
。
這樣就能很好的實現對映過程的複用。
二、TypeScript 中的對映型別是什麼?
1. 概念介紹
TypeScript 中的對映型別和數學中的對映類似,能夠將一個集合的元素轉換為新集合的元素,只是 TypeScript 對映型別是將一個型別對映成另一個型別。
在我們實際開發中,經常會需要一個型別的所有屬性轉換為可選型別,這時候你可以直接使用 TypeScript 中的 Partial
工具型別:
type User = {
name: string;
location: string;
age: number;
}
type User2 = Partial<User>;
/*
User2 的型別:
type User2 = {
name?: string | undefined;
location?: string | undefined;
age?: number | undefined;
}
*/
這樣我們就實現了將 User
型別對映成 User2
型別,並且將 User
型別中的所有屬性轉為可選型別。
2. 實現方法
TypeScript 對映型別的語法如下:
type TypeName<Type> = {
[Property in keyof Type]: boolean;
};
我們既然可以通過 Partial
工具型別非常簡單的實現將指定型別的所有屬性轉換為可選型別,那其內容原理又是如何?
我們可以在編輯器中,將滑鼠懸停在 Partial
名稱上面,可以看到編輯器提示如下:
拆解一下其中每個部分:
type Partial<T>
:定義一個型別別名Partial
和泛型T
;keyof T
:通過keyof
操作符獲取泛型T
中所有key
,返回一個聯合型別(如果不清楚什麼是聯合型別,可以理解為一個陣列);
type User = {
name: string;
location: string;
age: number;
}
type KeyOfUser = keyof User; // "name" | "location" | "age"
in
:類似 JS 中for...in
中的in
,用來遍歷目標型別的公開屬性名;T[P]
:是個索引訪問型別(也稱查詢型別),獲取泛型T
中P
型別,類似 JS 中的訪問物件的方式;?:
將型別值設定為可選型別;{ [P in keyof T] ?: T[P] | undefined}
:遍歷keyof T
返回的聯合型別,並定義用P
變數接收,其每次遍歷返回的值為可選型別的T[P]
。
這樣就實現了 Partial
工具型別,這種操作方法非常重要,是後面進行 TypeScript 型別體操的重要基礎。
關於型別體操的練習,有興趣可以看看這篇文章:
《這 30 道 TS 練習題,你能答對幾道?》https://juejin.cn/post/7009046640308781063
三、對映型別的應用
TypeScript 對映型別經常用來複用一些對型別的操作過程,比如 TypeScript 目前支援的 21 種工具型別,將我們常用的一些型別操作定義成這些工具型別,方便開發者複用這些型別。
所有已支援的工具型別可以看下官方文件:
https://www.typescriptlang.org/docs/handbook/utility-types.html
下面我們挑幾個常用的工具型別,看下其實現過程中是如何使用對映型別的。
在學習 TypeScript 過程中,推薦多在官方的 Playground 練習和學習:
https://www.typescriptlang.org/zh/play
1. Required 必選屬性
用來將型別的所有屬性設定為必選屬性。
實現如下:
type Required<T> = {
[P in keyof T]-?: T[P];
};
使用方式:
type User = {
name?: string;
location?: string;
age?: number;
}
type User2 = Required<User>;
/*
type User2 = {
name: string;
location: string;
age: number;
}
*/
const user: User2 = {
name: 'pingan8787',
age: 18
}
/*
報錯:
Property 'location' is missing in type '{ name: string; age: number; }'
but required in type 'Required<User>'.
*/
這邊的 -?
符號可以暫時理解為“將可選屬性轉換為必選屬性”,下一節會詳細介紹這些符號。
2. Readonly 只讀屬性
用來將所有屬性的型別設定為只讀型別,即不能重新分配型別。
實現如下:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
使用方式:
type User = {
name?: string;
location?: string;
age?: number;
}
type User2 = Readonly<User>;
/*
type User2 = {
readonly name?: string | undefined;
readonly location?: string | undefined;
readonly age?: number | undefined;
}
*/
const user: User2 = {
name: 'pingan8787',
age: 18
}
user.age = 20;
/*
報錯:
Cannot assign to 'age' because it is a read-only property.
*/
3. Pick 選擇指定屬性
用來從指定型別中選擇指定屬性並返回。
實現如下:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
使用如下:
type User = {
name?: string;
location?: string;
age?: number;
}
type User2 = Pick<User, 'name' | 'age'>;
/*
type User2 = {
name?: string | undefined;
age?: number | undefined;
}
*/
const user1: User2 = {
name: 'pingan8787',
age: 18
}
const user2: User2 = {
name: 'pingan8787',
location: 'xiamen', // 報錯
age: 18
}
/*
報錯
Type '{ name: string; location: string; age: number; }' is not assignable to type 'User2'.
Object literal may only specify known properties, and 'location' does not exist in type 'User2'.
*/
4. Omit 忽略指定屬性
作用類似與 Pick
工具型別相反,可以從指定型別中忽略指定的屬性並返回。
實現如下:
type Omit<T, K extends string | number | symbol> = {
[P in Exclude<keyof T, K>]: T[P];
}
使用方式:
type User = {
name?: string;
location?: string;
age?: number;
}
type User2 = Omit<User, 'name' | 'age'>;
/*
type User2 = {
location?: string | undefined;
}
*/
const user1: User2 = {
location: 'xiamen',
}
const user2: User2 = {
name: 'pingan8787', // 報錯
location: 'xiamen'
}
/*
報錯:
Type '{ name: string; location: string; }' is not assignable to type 'User2'.
Object literal may only specify known properties, and 'name' does not exist in type 'User2'.
*/
5. Exclude 從聯合型別中排除指定型別
用來從指定的聯合型別中排除指定型別。
實現如下:
type Exclude<T, U> = T extends U ? never : T;
使用方式:
type User = {
name?: string;
location?: string;
age?: number;
}
type User2 = Exclude<keyof User, 'name'>;
/*
type User2 = "location" | "age"
*/
const user1: User2 = 'age';
const user2: User2 = 'location';
const user3: User2 = 'name'; // 報錯
/*
報錯:
Type '"name"' is not assignable to type 'User2'.
*/
四、對映修飾符的應用
在自定義對映型別的時候,我們可以使用兩個對映型別的修飾符來實現我們的需求:
readonly
修飾符:將指定屬性設定為只讀型別;?
修飾符:將指定屬性設定為可選型別;
前面介紹 Readonly
和 Partial
工具型別的時候已經使用到:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P] | undefined;
}
當然,也可以對修飾符進行操作:
+
新增修飾符(預設使用);-
刪除修飾符;
比如:
type Required<T> = {
[P in keyof T]-?: T[P]; // 通過 - 刪除 ? 修飾符
};
也可以放在前面使用:
type NoReadonly<T> = {
-readonly [P in keyof T]: T[P]; // 通過 - 刪除 readonly 修飾符
}
五、總結
本文從數學中的對映作為切入點,詳細介紹 TypeScript 對映型別(Mapped Type)並介紹對映型別的應用和修飾符的應用。
在學習 TypeScript 型別系統時,儘量多和數學中的集合類比學習,比如 TypeScript 中的聯合型別,類似數學中的並集等。
學好對映型別,是接下來做型別體操中非常重要的基礎~~
參考資料
- TypeScript 文件-對映型別:https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
- TypeScript 工具型別: https://www.typescriptlang.org/docs/handbook/utility-types.html