網站搜尋

typescript - 撰寫通用多維 Map 類型


我想創建一個通用的多維地圖類型,其中 N 個類型中的最後一個是最終值,而前面的類型將實際上作為鍵。

換句話說:

let m:MDMap<Coat,Pie,Date,Location>;

//...

m.get(myWoolCoat,myApplePie,yesterday) // returns a Location

不使用可變參數泛型的話,看起來我最終會得到類似這樣的東西:

export type MultiMap<
    A extends any,
    B extends any,
    C = void,
    D = void,
    // ... etc.
> = D extends void 
      ? C extends void 
          ? Map<A,B>
          : Map<A,Map<B,C>>
      : Map<A,Map<B,Map<C,D>>>

let m:MultiMap<Coat,Pie,Date,Location>;
    

我知道 Typescript 可以處理可變長度的 元組 類型,但我還沒能將這個能力轉換成類似上述的需求。

我應該將就使用這個變通方法,還是有辦法實現某種遞歸泛型?

答案#1

TypeScript 沒有可變數量的泛型,因此無法寫出像 MultiMap<...K, V> 這樣的東西,意思是有任意數量的鍵類型參數和一個值類型參數。但正如你所指出的,確實存在可以保存任意有序類型列表的元組類型,因此你可能會對 MultiMap<K, V> 感到滿意,其中 K 是一個元組。因此,與其寫 MultiMap<Coat, Pie, Date, Location>,你可以寫成 MultiMap<[Coat, Pie, Date], Location>。反正也只多了幾個字符。

至於讓 MultiMap<[Coat, Pie, Date], Location> 對應到 Map<Coat, Map<Pie, Map<Date, Location>>>,我們可以透過將 MultiMap<K, V> 定義為遞迴條件型別來做到,並使用可變參數元組型別將 K 元組拆分為其第一個元素 F 和剩餘的元組 R:

type MultiMap<K extends any[], V> = K extends [infer F, ...infer R] ?
    Map<F, MultiMap<R, V>> : V;

語法 K extends [infer F, ...infer R] ? XXX : YYY 被稱為條件型別推斷,透過它我們可以讓編譯器推斷新的型別參數並引入作用域。K extends [infer F, ...infer R] ? XXX : YYY 的意思是我們希望編譯器嘗試將 K 匹配為一個變長元組,其具有某個初始元素 F 和剩餘元素 R。如果匹配成功,那麼在 XXX 型別表達式內,F 將是第一個元素的型別,而 R 將是一個更短的元組,表示剩下的元素。 也就是說,如果 K 是 [Coat, Pie, Date],那麼 F 將會是 Coat,而 R 將會是 [Pie, Date]。因此 Map<F, MultiMap<R, V>> 將會是 Map<Coat, MultiMap<[Pie, Date], V>>,你可以看到遞迴的發生。如果 K 是一個空的元組,那麼我們完全沒有鍵,只使用 V,這樣遞迴的基本情況就會如我們所願地終止。

讓我們確保它能正常運作:

type MyMap = MultiMap<[Coat, Pie, Date], Location>;
// type MyMap = Map<Coat, Map<Pie, Map<Date, Location>>>

看起來不錯!

程式碼的 Playground 連結