網站搜尋

TypeScript 中對 keyof 類型參數過濾


不需要了解 React,只需一些背景:在我的使用情境中,我希望用我自訂的高階組件來擴充使用者指定的 React.ComponentClass。為了做到這一點,我希望使用者也能傳給我一個清單,列出高階組件將要注入的特定 props 的名稱。這可以這樣做:

function listToComponentProps<TComponentProps, TKey = keyof TComponentProps>(props: Array<TKey>) {
  // Logic here, not important for the example
}
interface TestProps {a: number, b: Function, c: () => number}
listToComponentProps<TestProps>(['a', 'b', 'c'])

keyof 關鍵字為我處理了限制。listToComponentProps<TestProps> 的範例輸入將會是

  • 有效的:['b'],['b', 'c'],['c']
  • 無效

    • ['a']、['a', 'b']、['a', 'b', 'c'](a 是數字,不是函數)
    • [‘d’], [‘d’, ‘c’](d 不是介面 TestProps 的一部分)

問題是,我想要限制 props 參數,不僅要是 TComponentProps 的鍵,還要是對應到 TComponentProps 中類型為 Function 的鍵(這樣 'a' 就會被 TypeScript 編譯器檢測為無效選項)。如何才能實現這個任務?

答案#1

你可以這樣做:

const listToComponentProps = <
  TComponent extends {[P in TKey]: Function },
  TKey extends string = keyof TComponent>
  (props: TKey[]) => { /* ... */ };

interface TestProps {a: number, b: Function, c: () => number}
const result = listToComponentProps<TestProps>(['a', 'b', 'c']) // Type error

這會導致類型錯誤:

Type 'TestProps' does not satisfy the constraint '{ a: Function; b: Function; c: Function; }'.
  Types of property 'a' are incompatible.
    Type 'number' is not assignable to type 'Function'.

不幸的是,這種設置默認參數的方法最終限制了我們的 TComponent 只能擁有 Function 屬性。當你真正想要傳遞像 listToComponentProps<TestProps>(['b', 'c']) 這樣的東西時,這本應是有效的,你將需要明確填寫第二個類型參數,即 listToComponentProps<TestProps, 'b' | 'c'>(['b', 'c'])。

你真正想要的不是為 TKey 設定預設參數,而是希望泛型推斷更細緻:在類型參數列表中,所有可以被推斷的參數(例如 TKey,可以從傳入的陣列推斷)都應該被推斷,即使有些參數(在這個例子中是 TComponent)必須手動指定。TypeScript 現在並不是這樣運作,所以我們無計可施。

在 TypeScript 問題追蹤器上有一堆關於這個的未解決問題,你可以去找找它們並大肆抱怨。

如果你認為良好的推理比嚴格保留運行時特性更重要,你可以添加一個虛擬參數來使這兩個類型參數的推理分離:

const listToComponentProps =
    <TKey extends string>
    (props: TKey[]) =>
    <TComponent extends {[P in TKey]: Function }>
    () => { /* ... */ };

interface TestProps { a: number, b: Function, c: () => number }

const result = listToComponentProps(['a', 'b', 'c'])<TestProps>() // Type error
const result2 = listToComponentProps(['b', 'c'])<TestProps>() // OK