【TS】1294- 搞懂 TypeScript 中的映射類型(Mapped Types)

數(shù)學(xué)中的映射和 TS 中的映射類型的關(guān)系; TS 中映射類型的應(yīng)用; TS 中映射類型修飾符的應(yīng)用;
接下來會先從「數(shù)學(xué)中的映射」開始介紹。
本文使用到的 TypeScript 版本為 v4.6.2。
如果你對 TypeScript 還不熟悉,可以看下面幾篇資料:
一份不可多得的 TS 學(xué)習(xí)指南(1.8W字) 了不起的 TypeScript 入門教程
一、什么是映射?
在學(xué)習(xí) TypeScript 類型系統(tǒng)時,盡量多和數(shù)學(xué)中的集合類比學(xué)習(xí),比如 TypeScript 中的聯(lián)合類型,類似數(shù)學(xué)中的并集等。
在數(shù)學(xué)中,映射是指兩個元素的集合之間元素相互對應(yīng)的關(guān)系,比如下圖:
(來源:https://baike.baidu.com/item/%E6%98%A0%E5%B0%84/20402621)
可以將映射理解為函數(shù),如上圖,當(dāng)我們需要將集合 A 的元素轉(zhuǎn)換為集合 B 的元素,可以通過 f函數(shù)做映射,比如將集合 A 的元素 1對應(yīng)到集合 B 中的元素 2。這樣就能很好的實現(xiàn)映射過程的復(fù)用。
二、TypeScript 中的映射類型是什么?
1. 概念介紹
TypeScript 中的映射類型和數(shù)學(xué)中的映射類似,能夠?qū)⒁粋€集合的元素轉(zhuǎn)換為新集合的元素,只是 TypeScript 映射類型是將一個類型映射成另一個類型。
在我們實際開發(fā)中,經(jīng)常會需要一個類型的所有屬性轉(zhuǎn)換為可選類型,這時候你可以直接使用 TypeScript 中的 Partial工具類型:
type?User?=?{
??name:?string;
??location:?string;
??age:?number;
}
type?User2?=?Partial;
/*
? User2 的類型:
??
??type?User2?=?{
??????name?:?string?|?undefined;
??????location?:?string?|?undefined;
??????age?:?number?|?undefined;
??}
*/
這樣我們就實現(xiàn)了將 User類型映射成 User2類型,并且將 User類型中的所有屬性轉(zhuǎn)為可選類型。

2. 實現(xiàn)方法
TypeScript 映射類型的語法如下:
type?TypeName?=?{
??[Property?in?keyof?Type]:?boolean;
};
我們既然可以通過 Partial工具類型非常簡單的實現(xiàn)將指定類型的所有屬性轉(zhuǎn)換為可選類型,那其內(nèi)容原理又是如何?
我們可以在編輯器中,將鼠標(biāo)懸停在 Partial名稱上面,可以看到編輯器提示如下:

拆解一下其中每個部分:
type Partial:定義一個類型別名Partial和泛型T;keyof T:通過keyof操作符獲取泛型T中所有key,返回一個聯(lián)合類型(如果不清楚什么是聯(lián)合類型,可以理解為一個數(shù)組);
type?User?=?{
??name:?string;
??location:?string;
??age:?number;
}
type?KeyOfUser?=?keyof?User;?//?"name"?|?"location"?|?"age"
in:類似 JS 中for...in中的in,用來遍歷目標(biāo)類型的公開屬性名;T[P]:是個索引訪問類型(也稱查找類型),獲取泛型T中P類型,類似 JS 中的訪問對象的方式;?:將類型值設(shè)置為可選類型;{ [P in keyof T] ?: T[P] | undefined}:遍歷keyof T返回的聯(lián)合類型,并定義用P變量接收,其每次遍歷返回的值為可選類型的T[P]。
這樣就實現(xiàn)了 Partial工具類型,這種操作方法非常重要,是后面進行 TypeScript 類型體操的重要基礎(chǔ)。
關(guān)于類型體操的練習(xí),有興趣可以看看這篇文章:
《這 30 道 TS 練習(xí)題,你能答對幾道?》https://juejin.cn/post/7009046640308781063
三、映射類型的應(yīng)用
TypeScript 映射類型經(jīng)常用來復(fù)用一些對類型的操作過程,比如 TypeScript 目前支持的 21 種工具類型,將我們常用的一些類型操作定義成這些工具類型,方便開發(fā)者復(fù)用這些類型。
所有已支持的工具類型可以看下官方文檔:
https://www.typescriptlang.org/docs/handbook/utility-types.html
下面我們挑幾個常用的工具類型,看下其實現(xiàn)過程中是如何使用映射類型的。
在學(xué)習(xí) TypeScript 過程中,推薦多在官方的 Playground 練習(xí)和學(xué)習(xí):
https://www.typescriptlang.org/zh/play
1. Required 必選屬性
用來將類型的所有屬性設(shè)置為必選屬性。
實現(xiàn)如下:
type?Required?=?{
????[P?in?keyof?T]-?:?T[P];
};
使用方式:
type?User?=?{
??name?:?string;
??location?:?string;
??age?:?number;
}
type?User2?=?Required;
/*
??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'.
*/
這邊的 -?符號可以暫時理解為“將可選屬性轉(zhuǎn)換為必選屬性”,下一節(jié)會詳細(xì)介紹這些符號。
2. Readonly 只讀屬性
用來將所有屬性的類型設(shè)置為只讀類型,即不能重新分配類型。
實現(xiàn)如下:
type?Readonly?=?{
??readonly?[P?in?keyof?T]:?T[P];
}
使用方式:
type?User?=?{
??name?:?string;
??location?:?string;
??age?:?number;
}
type?User2?=?Readonly;
/*
??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 選擇指定屬性
用來從指定類型中選擇指定屬性并返回。
實現(xiàn)如下:
type?Pickextends?keyof?T>?=?{
??[P?in?K]:?T[P];
}
使用如下:
type?User?=?{
??name?:?string;
??location?:?string;
??age?:?number;
}
type?User2?=?Pick'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工具類型相反,可以從指定類型中忽略指定的屬性并返回。
實現(xiàn)如下:
type?Omitextends?string?|?number?|?symbol>?=?{
??[P?in?Exclude]:?T[P];
}
使用方式:
type?User?=?{
??name?:?string;
??location?:?string;
??age?:?number;
}
type?User2?=?Omit'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 從聯(lián)合類型中排除指定類型
用來從指定的聯(lián)合類型中排除指定類型。
實現(xiàn)如下:
type?Exclude?=?T?extends?U???never?:?T;
使用方式:
type?User?=?{
??name?:?string;
??location?:?string;
??age?:?number;
}
type?User2?=?Exclude'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'.
*/
四、映射修飾符的應(yīng)用
在自定義映射類型的時候,我們可以使用兩個映射類型的修飾符來實現(xiàn)我們的需求:
readonly修飾符:將指定屬性設(shè)置為只讀類型;?修飾符:將指定屬性設(shè)置為可選類型;
前面介紹 Readonly和 Partial工具類型的時候已經(jīng)使用到:
type?Readonly?=?{
??readonly?[P?in?keyof?T]:?T[P];
}
type?Partial?=?{
??[P?in?keyof?T]?:?T[P]?|?undefined;
}
當(dāng)然,也可以對修飾符進行操作:
+添加修飾符(默認(rèn)使用);-刪除修飾符;
比如:
type?Required?=?{
????[P?in?keyof?T]-?:?T[P];?//?通過?-?刪除???修飾符
};
也可以放在前面使用:
type?NoReadonly?=?{
??-readonly?[P?in?keyof?T]:?T[P];?//?通過?-?刪除?readonly?修飾符
}
五、總結(jié)
本文從數(shù)學(xué)中的映射作為切入點,詳細(xì)介紹 TypeScript 映射類型(Mapped Type)并介紹映射類型的應(yīng)用和修飾符的應(yīng)用。
在學(xué)習(xí) TypeScript 類型系統(tǒng)時,盡量多和數(shù)學(xué)中的集合類比學(xué)習(xí),比如 TypeScript 中的聯(lián)合類型,類似數(shù)學(xué)中的并集等。
學(xué)好映射類型,是接下來做類型體操中非常重要的基礎(chǔ)~~
參考資料
TypeScript 文檔-映射類型:https://www.typescriptlang.org/docs/handbook/2/mapped-types.html TypeScript 工具類型:https://www.typescriptlang.org/docs/handbook/utility-types.html
