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
