深入了解TypeScript中的映射類(lèi)型
DRY 原則(Don't repeat yourself)是軟件開(kāi)發(fā)中最重要的原則之一,即不要重復(fù)自己。應(yīng)該避免在代碼中的兩個(gè)或多個(gè)地方存在重復(fù)的業(yè)務(wù)邏輯。
在 TypeScript 中,映射類(lèi)型可以幫助我們避免編寫(xiě)重復(fù)的代碼,它可以根據(jù)現(xiàn)有類(lèi)型和定義的一些規(guī)則來(lái)創(chuàng)建新類(lèi)型。下面就來(lái)看一下什么是映射類(lèi)型以及如何構(gòu)建自己的映射類(lèi)型。
1. 基本概念
在介紹映射類(lèi)型之前,先來(lái)看一些前置知識(shí)。
(1)索引訪(fǎng)問(wèn)類(lèi)型
在 TypeScript 中,我們可以通過(guò)按名稱(chēng)查找屬性來(lái)訪(fǎng)問(wèn)它的類(lèi)型:
type?AppConfig?=?{ ??username:?string; ??layout:?string; }; type?Username?=?AppConfig["username"];
在這個(gè)例子中,通過(guò) AppConfig
類(lèi)型的索引 username
獲取到其類(lèi)型 string
,類(lèi)似于在 JavaScript 中通過(guò)索引來(lái)獲取對(duì)象的屬性值。
(2)索引簽名
當(dāng)類(lèi)型屬性的實(shí)際名稱(chēng)是未知的,但它們將引用的數(shù)據(jù)類(lèi)型已知時(shí),索引簽名就很方便。
type?User?=?{ ??name:?string; ??preferences:?{ ????[key:?string]:?string; ??} }; const?currentUser:?User?=?{ ??name:?'Foo?Bar', ??preferences:?{ ????lang:?'en', ??}, }; const?currentLang?=?currentUser.preferences.lang;
在上面的例子中,currentLang 的類(lèi)型是 string 而不是 any。此功能與 keyof 運(yùn)算符一起搭配使用是使映射類(lèi)型成為可能的核心之一。
(3)聯(lián)合類(lèi)型
聯(lián)合類(lèi)型是兩種或多種類(lèi)型的組合。它表明值的類(lèi)型可以是聯(lián)合中包含的任何一種類(lèi)型。
type?StringOrNumberUnion?=?string?|?number; let?value:?StringOrNumberUnion?=?'hello,?world!'; value?=?100;
下面是一個(gè)更復(fù)雜的例子,編譯器可以為聯(lián)合類(lèi)型提供一些高級(jí)保護(hù):
type?Animal?=?{ ??name:?string; ??species:?string; }; type?Person?=?{ ??name:?string; ??age:?number; }; type?AnimalOrPerson?=?Animal?|?Person; const?value:?AnimalOrPerson?=?loadFromSomewhereElse(); console.log(value.name);???//?? console.log(value.age);????//?? if?('age'?in?value)?{ ??console.log(value.age);?//?? }
在這個(gè)例子中,因?yàn)?Animal 和 Person 都有 name 屬性,所以第 15 行的 value.name
可以正常輸出,沒(méi)有錯(cuò)誤。而第 16 行的 value.age
會(huì)編譯錯(cuò)誤,因?yàn)槿绻?value 是 Animal 類(lèi)型,則 value 是沒(méi)有 age 屬性的。在第 19 行的 if 塊中,因?yàn)橹挥?value 存在 age 屬性才能進(jìn)入這個(gè)代碼塊。所以,在這個(gè) if 塊中,value 一定是 Person,TS 可以知道 value 一定是具有 age 屬性的,所以編譯正確。
(4)keyof 類(lèi)型運(yùn)算符
keyof
類(lèi)型運(yùn)算符返回傳遞給它的類(lèi)型的 key 的聯(lián)合。
type?AppConfig?=?{ ??username:?string; ??layout:?string; }; type?AppConfigKey?=?keyof?AppConfig;
在這個(gè)例子中,AppConfigKey
類(lèi)型會(huì)被解析為"username" | "layout"
。它可以與索引簽名一起使用:
type?User?=?{ ??name:?string; ??preferences:?{ ????[key:?string]:?string; ??} }; type?UserPreferenceKey?=?keyof?User["preferences"];
這里,UserPreferenceKey
類(lèi)型被解析為 string | number
。
(5)元組類(lèi)型
元組是一種特殊的數(shù)組類(lèi)型,其中數(shù)組的元素可能是特定索引處的特定類(lèi)型。它們?cè)试S TypeScript 編譯器圍繞值數(shù)組提供更高的安全性,尤其是當(dāng)這些值屬于不同類(lèi)型時(shí)。
例如,TypeScript 編譯器能夠?yàn)樵M的各種元素提供類(lèi)型安全:
type?Currency?=?[number,?string]; const?amount:?Currency?=?[100,?'USD']; function?add(values:?number[])?{ ???return?values.reduce((a,?b)?=>?a?+?b); } add(amount); //?Error:?Argument?of?type?'Currency'?is?not?assignable?to?parameter?of?type?'number[]'. //?Type?'string'?is?not?assignable?to?type?'number'.
上面的代碼中會(huì)報(bào)錯(cuò),Currency
類(lèi)型的參數(shù)不能分配給“number[]
”類(lèi)型的參數(shù),string
類(lèi)型不能分配給 number
類(lèi)型。
當(dāng)訪(fǎng)問(wèn)超出元組定義類(lèi)型的索引處的元素時(shí),TypeScript 能夠進(jìn)行提示:
type?LatLong?=?[number,?number];? const?loc:?LatLong?=?[48.858370,?2.294481]; console.log(loc[2]); //?Error:?Tuple?type?'LatLong'?of?length?'2'?has?no?element?at?index?'2'.
這里,元組類(lèi)型 LatLong 只有兩個(gè)元素,當(dāng)試圖訪(fǎng)問(wèn)第三個(gè)元素時(shí),就會(huì)報(bào)錯(cuò)。
(6)條件類(lèi)型
條件類(lèi)型是一個(gè)表達(dá)式,類(lèi)似于 JavaScript 中的三元表達(dá)式,其語(yǔ)法如下:
T?extends?U???X?:?Y
來(lái)看一個(gè)實(shí)際的例子:
type?ConditionalType?=?string?extends?boolean???string?:?boolean;
在上面的示例中,ConditionalType 的類(lèi)型將是 boolean,因?yàn)闂l件string extends boolean 是始終為 false。
2. 映射類(lèi)型
(1)初體驗(yàn)
在 TypeScript 中,當(dāng)需要從另一種類(lèi)型派生(并保持同步)另一種類(lèi)型時(shí),使用映射類(lèi)型會(huì)特別有用。
//?用戶(hù)的配置值 type?AppConfig?=?{ ??username:?string; ??layout:?string; }; //?用戶(hù)是否有權(quán)更改配置值 type?AppPermissions?=?{ ??changeUsername:?boolean; ??changeLayout:?boolean; };
在上面的代碼中,AppConfig 和 AppPermissions 之間是存在隱式關(guān)系的,每當(dāng)向 AppConfig 添加新的配置值時(shí),AppPermissions 中也必須有相應(yīng)的布爾值。
這里可以使用映射類(lèi)型來(lái)管理兩者之間的關(guān)系:
type?AppConfig?=?{ ??username:?string; ??layout:?string; }; type?AppPermissions?=?{ ??[Property?in?keyof?AppConfig?as?`change${Capitalize<Property>}`]:?boolean };
在上面的代碼中,只要 AppConfig 中的類(lèi)型發(fā)生變化,AppPermissions 就會(huì)隨之變化。實(shí)現(xiàn)了兩者之間的映射關(guān)系。
(2)概念
在 TypeScript 和 JavaScript 中,最常見(jiàn)的映射就是 Array.prototype.map():
[1,?2,?3].map(value?=>?value.toString());?//?["1",?"2",?"3"]
這里,我們將數(shù)組中的數(shù)字映射到其字符串的表示形式。因此,TypeScript 中的映射類(lèi)型意味著將一種類(lèi)型轉(zhuǎn)換為另一種類(lèi)型,方法就是對(duì)其每個(gè)屬性進(jìn)行轉(zhuǎn)換。
(3)實(shí)例
下面來(lái)通過(guò)一個(gè)例子來(lái)深入理解一下映射類(lèi)型。對(duì)設(shè)備定義以下類(lèi)型,其包含制造商和價(jià)格屬性:
type?Device?=?{ ??manufacturer:?string; ??price:?number; };
為了讓用戶(hù)更容易理解設(shè)備信息,因此為對(duì)象添加一個(gè)新類(lèi)型,該對(duì)象可以使用適當(dāng)?shù)母袷絹?lái)格式化設(shè)備的每個(gè)屬性:
type?DeviceFormatter?=?{ ??[Key?in?keyof?Device?as?`format${Capitalize<Key>}`]:?(value:?Device[Key])?=>?string; };
我們來(lái)拆解一下上面的代碼。Key in keyof Device
使用 keyof 類(lèi)型運(yùn)算符生成 Device 中所有鍵的并集。將它放在索引簽名中實(shí)際上是遍歷 Device 的所有屬性并將它們映射到 DeviceFormatter 的屬性。
format${Capitalize<Key>}
是映射的轉(zhuǎn)換部分,它使用 key 重映射和模板文字類(lèi)型將屬性名稱(chēng)從 x 更改為 formatX。
(value: Device[Key]) => string;
利用索引訪(fǎng)問(wèn)類(lèi)型 Device[Key]
來(lái)指示格式化函數(shù)的 value 參數(shù)是格式化的屬性的類(lèi)型。因此,formatManufacturer 接受一個(gè) string(制造商),而 formatPrice 接受一個(gè)number(價(jià)格)。
下面是 DeviceFormatter 類(lèi)型的樣子:
type?DeviceFormatter?=?{ ??formatManufacturer:?(value:?string)?=>?string; ??formatPrice:?(value:?number)?=>?string; };
現(xiàn)在,假設(shè)將第三個(gè)屬性 releaseYear 添加到 Device 類(lèi)型中:
type?Device?=?{ ??manufacturer:?string; ??price:?number; ??releaseYear:?number; }
由于映射類(lèi)型的強(qiáng)大功能,DeviceFormatter 類(lèi)型會(huì)自動(dòng)擴(kuò)展為如下類(lèi)型,無(wú)需進(jìn)行任何額外的工作:
type?DeviceFormatter?=?{ ??formatManufacturer:?(value:?string)?=>?string; ??formatPrice:?(value:?number)?=>?string; ??formatReleaseYear:?(value:?number)?=>?string; };
3. 實(shí)用程序中的映射
TypeScript 附帶了許多用作實(shí)用程序的映射類(lèi)型,最常見(jiàn)的包括 Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable、ReturnType 等。下面來(lái)看看其中的兩個(gè)是如何構(gòu)建的。
(1)Partial
Partial 是一種映射類(lèi)型,可以將已有的類(lèi)型屬性轉(zhuǎn)換為可選類(lèi)型,并通過(guò)使用與 undefined 的聯(lián)合使類(lèi)型可以為空。
interface?Point3D?{ ????x:?number; ????y:?number; ????z:?number; } type?PartialPoint3D?=?Partial<Point3D>;
這里的 PartialPoint3D
類(lèi)型實(shí)際是這樣的:
type?PartialPoint3D?=?{ ????x?:?number; ????y?:?number; ????z?:?number; }
當(dāng)我們鼠標(biāo)懸浮在 Partial 上時(shí),就會(huì)看到它的定義:
把它拿出來(lái):
type?Partial<T>?=?{?[P?in?keyof?T]?:?T[P]?|?undefined;?}
下面來(lái)拆解一下這行代碼:
- 使用泛型來(lái)傳遞目標(biāo)接口 T;
- 使用
keyof T
來(lái)獲取 T 的所有 key。 - 通過(guò)使用
[P in keyof T]
來(lái)訪(fǎng)問(wèn)并循環(huán)所有的 key; - 它通過(guò)添加 ? 使 key 成為可選的。
- 使用聯(lián)合類(lèi)型
T[P] | undefined
使 key 的類(lèi)型可以為空;
(2)Exclude
Exclude 是一種映射類(lèi)型,可讓有選擇地從類(lèi)型中刪除屬性。其定義如下:
type?Exclude<T,?U>?=?T?extends?U???never?:?T
它通過(guò)使用條件類(lèi)型從 T 中排除那些可分配給 U 的類(lèi)型,并且在排除的屬性上返回 nerver。
type?animals?=?'bird'?|?'cat'?|?'crocodile'; type?mamals?=?Exclude<animals,?'crocodile'>;??//?'bird'?|?'cat'
4. 構(gòu)建映射類(lèi)型
通過(guò)上面的對(duì) TypeScript 內(nèi)置實(shí)用程序類(lèi)型的原理解釋?zhuān)瑢?duì)映射類(lèi)型有了更深的理解。最后,我們來(lái)構(gòu)建一個(gè)自己的映射類(lèi)型:Optional
,它可以將原類(lèi)型中指定 key 的類(lèi)型置為可選的并且可以為空。
我們可以這樣做:
- 將整個(gè)類(lèi)型轉(zhuǎn)換為 Optional
- 從該新類(lèi)型中僅選擇想要的屬性使其成為可選的。
- 將原始類(lèi)型與排除的屬性連接起來(lái)。
實(shí)現(xiàn)代碼及測(cè)試用例如下:
type?Optional<T,?K?extends?keyof?T>?=?Pick<Partial<T>,?K>?&?Omit<T,?K>; type?Person?=?{ ??name:?string; ??surname:?string; ??email:?string; } ?? type?User?=?Optional<Person,?'email'>; //?現(xiàn)在?email?屬性是可選的 type?AnonymousUser?=?Optional<Person,?'name'?|?'surname'>; //?現(xiàn)在?email?和?surname?屬性是可選的
注意,這里使用 K extends keyof T
來(lái)確保只能傳遞屬于類(lèi)型/接口的屬性。否則,TypeScript 將在編譯時(shí)拋出錯(cuò)誤。
映射類(lèi)型的一大優(yōu)點(diǎn)就是它們的可組合性:可以組合它們來(lái)創(chuàng)建新的映射類(lèi)型。
上面使用了已有的實(shí)用程序類(lèi)型實(shí)現(xiàn)了我們想要的 Optional。當(dāng)然,我們也可以在不使用任何其他映射類(lèi)型的情況下重新創(chuàng)建 Optional 映射類(lèi)型實(shí)用程序:
type?Optional<T,?K?extends?keyof?T>?= ????{?[P?in?K]?:?T[P]?} ????& ????{?[P?in?Exclude<keyof?T,?K>]:?T[P]?};
上面的代碼結(jié)合了兩種類(lèi)型:
- 第一種類(lèi)型通過(guò)使用
?
修飾符使 T 的所有 K 的 key 都是可選的。 - 第二種類(lèi)型通過(guò)使用
Excluse<keyof T,K>
來(lái)獲取剩余的key。
以上就是深入了解TypeScript中的映射類(lèi)型的詳細(xì)內(nèi)容,更多關(guān)于TypeScript映射類(lèi)型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用js操作css實(shí)現(xiàn)js改變背景圖片示例
有個(gè)朋友在weibo上問(wèn)我可不可以用JS和CSS讓頁(yè)面每次刷新隨機(jī)產(chǎn)生一張背景圖,當(dāng)然是可以的。具體的方法看下面的實(shí)現(xiàn)代碼吧2014-03-03JavaScript實(shí)現(xiàn)限時(shí)秒殺功能
各種電商活動(dòng)都喜換選擇限時(shí)秒殺活動(dòng)形式,這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)限時(shí)秒殺功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08關(guān)于layui toolbar和template的結(jié)合使用方法
今天小編就為大家分享一篇關(guān)于layui toolbar和template的結(jié)合使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09用js實(shí)現(xiàn)輸入提示(自動(dòng)完成)的實(shí)例代碼
用js實(shí)現(xiàn)輸入提示(自動(dòng)完成)的實(shí)例代碼,需要的朋友可以參考一下2013-06-06Javascript類(lèi)型系統(tǒng)之String字符串類(lèi)型詳解
這篇文章主要介紹了Javascript類(lèi)型系統(tǒng)之String字符串類(lèi)型詳解的相關(guān)資料,需要的朋友可以參考下2016-06-06javascript對(duì)象的property和prototype是這樣一種關(guān)系
javascript對(duì)象的property和prototype是這樣一種關(guān)系...2007-03-03