網站搜尋

泛型 - 將 TypeScript 聯合類型分解為具體類型


我想知道是否有可能在 TypeScript 中將聯合類型「拆分」成特定的子類型。這是我嘗試使用的代碼,從這段代碼片段應該很明顯我想要達到的目標:

type SplitType<T> =
  T extends (infer A)|(infer B)
  ? Something<A, B>
  : T;

在這個例子中,Something<A, B> 可能是 [A, B],也可能是完全不同的類型。這意味著 SplitType<string> 只會輸出一個 string,但是 SplitType<number|string> 將意味着 [number, string]。

在 TypeScript 中有可能做到類似的事情嗎?如果不行,未來有沒有可能會有允許這種操作的功能(例如可變數類型)?

答案#1

有了 Matt McCutchen 的回答,以及自 v4.1 起的 遞迴條件型別:

type UnionToParm<U> = U extends any ? (k: U) => void : never;
type UnionToSect<U> = UnionToParm<U> extends ((k: infer I) => void) ? I : never;
type ExtractParm<F> = F extends { (a: infer A): void } ? A : never;

type SpliceOne<Union> = Exclude<Union, ExtractOne<Union>>;
type ExtractOne<Union> = ExtractParm<UnionToSect<UnionToParm<Union>>>;

type ToTuple<Union> = ToTupleRec<Union, []>;
type ToTupleRec<Union, Rslt extends any[]> = 
    SpliceOne<Union> extends never ? [ExtractOne<Union>, ...Rslt]
    : ToTupleRec<SpliceOne<Union>, [ExtractOne<Union>, ...Rslt]>
;

type test = ToTuple<5 | 6 | "l">;

答案#2

對於固定的聯合成員最大數量,我們可以通過生成調用簽名的交集,然後將其與具有多個調用簽名的類型匹配,來以單一實現定義的順序提取聯合成員。此版本僅在啟用 strictFunctionTypes 時有效。

// https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type UnionToFunctions<U> =
    U extends unknown ? (k: U) => void : never;

type IntersectionOfFunctionsToType<F> =
    F extends { (a: infer A): void; (b: infer B): void; (c: infer C): void; } ? [A, B, C] :
    F extends { (a: infer A): void; (b: infer B): void; } ? [A, B] :
    F extends { (a: infer A): void } ? [A] :
    never;

type SplitType<T> =
    IntersectionOfFunctionsToType<UnionToIntersection<UnionToFunctions<T>>>;

type Test1 = SplitType<number>;                    // [number]
type Test2 = SplitType<number | string>;           // [string, number]
type Test3 = SplitType<number | string | symbol>;  // [string, number, symbol]