首頁  >  問答  >  主體

如何將類型指派給元組數組,其條目可能因元組而異?

假設我有function foo(args) {...},其中args是一個二元組數組,這樣元組中的條目是相同類型的(即[T,T]),但是元組的條目可能任意變化(即[[T,T], [U,U ],[V,V]])。例如:

foo([
  [1, 3],
  ["hello", "world"],
  [true, true],
  [2, 7]
]) // no error

我應該如何輸入 fooargs 參數,以便元組中的不匹配類型引發編譯時類型錯誤?例如:

foo([
  [1, 3],
  ["hello", 5], // type error here
  [true, true],
  [2, 7n] // type error here
])

如果無法內聯顯示類型錯誤,則使整個函數呼叫錯誤也是可以接受的。


附錄:是否可以使用[SomeType, T] 類型的2 元組(即第二個條目的類型應與第一個),但T仍然可以在元組之間變化[[SomeType, T],[SomeType, U],[SomeType, V]]?

foo([
  [{value: 1}, 3],
  [{value: "hello"}, 5], // type error here
  [{value: true}, true],
  [{value: 2}, 7n] // type error here
])

P粉068486220P粉068486220409 天前522

全部回覆(2)我來回復

  • P粉948258958

    P粉9482589582023-09-07 19:10:47

    我認為您可以透過為row 建立一個類型來簡單地實現此目的,該類型將接受stringnumber 或<代码>布爾值。

    型別 Row = string[] |布林值[] |數字[]

    現在,我們可以為 foo 函數的 args 參數指派此類型。

    function foo(args: Row[]): void {
     ...
     ...
     ...
    }

    使用此類型定義,如果您向 foo 提供一個參數,其中行中元素的類型不匹配,Typescript 將引發錯誤。

    這裡是遊樂場連結.

    回覆
    0
  • P粉136356287

    P粉1363562872023-09-07 13:38:15

    為了實現這一點,我們需要使用 泛型陣列和映射類型來映射陣列的元素。由於我們知道該數組應該是長度為 2 的元組數組,因此我們將推斷元組中第一項的泛型參數,並使第二項具有相同類型。要取得泛型參數的類型,我們需要使用 推斷關鍵字。請注意,我們需要確切地知道(或至少是具有相似形狀的類型)用於使其工作的泛型類型,在我們的例子中是 Variable

    const foo = <T extends unknown[][]>(arr: {
      [K in keyof T]: T[K] extends unknown[]
        ? T[K][0] extends Variable<infer Type>
          ? [Variable<Type>, Type]
          : T[K]
        : T[K];
      }) => {}

    看起來似乎就是全部,但是讓我們看看以下數組的類型:

    const arr = [1, '2', false];
    // (string | number | boolean)[]
    type Arr = typeof arr;

    如您所見,該類型與我們在 arr 中的類型不完全相同。編譯器擴展了類型以確保我們可以改變數組元素。為了讓編譯器知道該陣列是唯讀的,我們需要使用 const 斷言

    const arr = [1, '2', false] as const;
    // readonly [1, "2", false]
    type Arr = typeof arr;

    現在看起來不錯,這意味著我們需要將傳遞給foo 的數組設定為只讀`,並且由於只讀取組是我們將得到的可變數組的超集如果我們嘗試將只讀取組傳遞給數組,則會出現錯誤:

    // false
    type Case1 = readonly number[] extends number[] ? true : false;
    // true
    type Case2 = number[] extends readonly number[] ? true : false;

    因此,我們將 foo 中的所有陣列類型更新為唯讀。請注意,由於我們的數組是二維的,因此內部數組也將是唯讀的,並且數組的約束應該是只讀數組的只讀數組:

    const foo = <T extends readonly (readonly unknown[])[]>(arr: {
      [K in keyof T]: T[K] extends readonly unknown[]
        ? T[K][0] extends Variable<infer Type>
          ? readonly [Variable<Type>, Type]
          : T[K]
        : T[K];
    }) => {};

    測試:

    declare const ctx1: Variable<number>;
    declare const ctx2: Variable<string>;
    declare const ctx3: Variable<boolean>;
    declare const ctx4: Variable<number>;
    declare const ctx5: Variable<number[]>;
    declare const ctx6: Variable<{ name: string; age: number }>;
    
    foo([
      [ctx1, 3],
      [ctx2, 'world'],
      [ctx3, true],
      [ctx4, 7],
    ] as const);
    
    foo([
      [ctx1, 3],
      [ctx2, 'world'],
      [ctx3, true],
      [ctx4, 'invalid'], // error
    ] as const);

    但是,我們仍然存在一些問題。例如,如果元組中的第一個元素是Variable<7> ,則表示第二個參數也應該是7,而不是任何數字,如果這是一個問題我們需要取得7 的原語,即數字。這可以使用 ToPrimitive 來自我的 type-samurai 開源專案的實用程式類型:

    type ToPrimitive<T> = T extends string
      ? string
      : T extends number
      ? number
      : T extends null
      ? null
      : T extends undefined
      ? undefined
      : T extends boolean
      ? boolean
      : T extends bigint
      ? bigint
      : T extends symbol
      ? symbol
      : {
          [K in keyof T]: ToPrimitive<T[K]>;
        };

    更新功能:

    const foo = <T extends readonly (readonly unknown[])[]>(arr: {
      [K in keyof T]: T[K] extends readonly unknown[]
        ? T[K][0] extends Variable<infer Type>
          ? ToPrimitive<Type> extends infer PrimitiveType
            ? readonly [Variable<PrimitiveType>, PrimitiveType]
            : T[K]
          : T[K]
        : T[K];
    }) => {};

    另一個問題是,如果在我們目前的 foo 實作中推斷的類型是 number[],我們將不會允許只讀取組:

    foo([
      [ctx5, [4, 5, 6]], // The type 'readonly [4, 5, 6]' is 'readonly' and cannot be assigned to the mutable type 'number[]'
    ] as const)

    修復非常簡單,我們將檢查推斷的類型是否是某個數組,然後獲取其元素類型並將 readonly ElemenType[] 作為元組中的第二個參數:

    const foo = <T extends readonly (readonly unknown[])[]>(arr: {
      [K in keyof T]: T[K] extends readonly unknown[]
        ? T[K][0] extends Variable<infer Type>
          ? ToPrimitive<Type> extends infer PrimitiveType
            ? readonly [
                Variable<PrimitiveType>,
                PrimitiveType extends Array<infer ArrayItem>
                  ? readonly ArrayItem[]
                  : PrimitiveType,
              ]
            : T[K]
          : T[K]
        : T[K];
    }) => {};

    測試:

    foo([
      [ctx1, 3],
      [ctx2, 'world'],
      [ctx3, true],
      [ctx4, 7],
      [ctx5, [4, 5, 6]],
      [ctx6, {name: "Hi", age: 23}],
    ] as const);
    
    foo([
      [ctx1, 3],
      [ctx2, 'world'],
      [ctx3, true],
      [ctx4, true], // error here
      [ctx5, [4, 5, 6]],
      [ctx6, 50], // error here
    ] as const);

    令人煩惱的部分是我們需要在任何地方使用 const 斷言。在 Typescript 5.0 中,const 類型參數,這樣我們就可以避免 const 斷言

    const foo = <const T extends readonly unknown[]>(item: T) => item
    // readonly [1, 2, 3] 
    const result = foo([1,2,3])

    不幸的是,我們無法使用它們,因為我們對參數進行了一些操作,而不是直接將 T 作為類型分配給它:

    const foo = <const T extends readonly unknown[]>(item: {[K in keyof T]: T[K]}) => item
    
    // const result: (2 | 1 | 3)[]
    const result = foo([1, 2, 3])

    總之,目前,const 斷言是確保其按預期工作的唯一方法。

    連結到遊樂場

    #

    回覆
    0
  • 取消回覆