某一天,一個朋友忽然在微信裏問我,「['TYPE'] => { 'TYPE': 'TYPE' },寫個函數轉換這個,類型怎麼加?」。筆者立刻的回覆就是,「用 reduce 將數組轉對象,TS 類型寫個泛型便可」。html
說是這麼說,可是發如今寫 TS 類型上實際上卻的確不是那麼容易,因而,筆者立刻偷偷開始嘗試。(推薦你們能夠在 TypeScript Playground 上練習 TS 類型)typescript
首先轉換從數組轉換到對象,咱們用 reduce 很容易達成目標:數組
function convert(source) {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {});
}
複製代碼
但本文的重點是寫這個函數的類型。簡而言之,就是須要寫 convert 函數參數的類型和返回值類型,從而能夠定義函數的形狀。微信
因而,筆者折騰了一會,寫出來下述類型,編輯器
function convert<K extends string>(source: K[]): Record<K, K> {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
type TNAME = 'LIN' | 'HUA';
const peoples: TNAME[] = ['LIN', 'HUA'];
const peopleMap = convert<TNAME>(peoples);
function getNameFromPeopleMap<T extends any>(name: T): T {
return peopleMap[name];
}
const t = getNameFromPeopleMap('HUA');
複製代碼
新增一個 getNameFromPeopleMap 函數,編寫泛型 (name: T): T。從而達成了如下效果:
函數
因此傳入任意值,編輯器都不會提示類型錯誤。
優化
所以,上述類型若是打個分,連及格線都不達。因而,進行考慮優化。爲了解決上述兩個問題,一段時間後,新的類型火熱出爐!ui
function convert<K extends string>(source: readonly K[]): Record<K, K> {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
const peoples = ['LIN', 'HUA'] as const;
const peopleMap = convert<typeof peoples[number]>(peoples);
function getNameFromPeopleMap<T extends typeof peoples[number]>(name: T): T {
return peopleMap[name] as any;
}
const t = getNameFromPeopleMap('LIN');
複製代碼
爲了減小冗餘的 TNAME。使用了 TypeScript 3.4 版本新增的 const 斷言。它能夠將咱們一個字面量表達式斷言爲一個 TS 類型。以下:spa
// Type '"HELLO"'
const STR = "HELLO" as const;
// Type 'readonly [10, 20]'
const POINT = [10, 20] as const;
// Type '{ readonly text: "hello" }'
const PAYLOAD = { text: "hello" } as const;
複製代碼
所以,無需 TNAME[],咱們的 peoples 擁有了 TS 類型。
3d
同時,修改 getNameFromPeopleMap 的函數泛型聲明爲 <T extends typeof peoples[number]>(name: T): T。咱們便可約束函數傳入值只能爲 peoples 的子項。
可能有些同窗比較困惑,上述泛型是什麼意思。筆者這裏稍微解釋一下。
那麼 T extends 即表明着進行泛型約束。所以咱們就能夠成功對函數傳參進行類型限制。
瞧一瞧,多麼成功。當我自信的把這段代碼發給朋友時,又遭到了無情吐槽,「我想要的是 peopleMap 直接使用」
的確,筆者反思了一下,爲何須要莫名其妙多一個函數去取值呢?還不是由於沒法從 convert 函數上完成傳參值和返回值的相等的類型限制嘛。
嗯,若是能從 convert 函數上直接支持就行了。
因而筆者將目光挪到了可疑的 convert 函數類型 (source: readonly K[]): Record<K, K> 上。首先從 Record<K, K> 上看,已經沒法約束類型了。按照需求,返回值應該是 key 與 value 相等的對象類型。怎麼寫呢?筆者查閱了 TS 文檔後,得出一種寫法:
type IRecord<K extends keyof any> = {
[P in K]: P;
}
複製代碼
可能你們好奇 keyof any 是什麼?其實它剛恰好就是對象的 key 類型。由於 TS 限制對象 key 類型只能爲 string、number、symbol。
使用效果以下:
所以最終能夠幹掉以前多餘的函數,得出以下類型:
type IRecord<K extends keyof any> = {
[P in K]: P;
}
function convert<K extends string>(source: readonly K[]): IRecord<K> {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
const peoples = ['LIN', 'HUA'] as const;
const peopleMap = convert<typeof peoples[number]>(peoples);
const t = peopleMap.LIN;
複製代碼
同時也達到了對 peopleMap 的取值類型限制。
function convert<K extends keyof any>(source: readonly K[]): { [P in K]: P }
{
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
const peoples = ['LIN', 'HUA'] as const;
const peopleMap = convert(peoples);
const t = peopleMap.LIN;
複製代碼
簡簡單單的一個 TS 類型的需求,發現編寫起來仍是挺有意思的。第一,做爲筆記;第二,也但願能夠幫助到有須要的同窗們。
謝謝你們~若有助益,不勝榮幸!