[英]inference not working on type argument from a higher order function
我已經為這個問題精簡了這段代碼。 這里是游樂場。
我有這個函數,它返回一個帶有類型參數的函數:
export function createInteractor<E extends Element>(name: string) {
return <S extends InteractorSpecification<E, S>>(specification: InteractorSpecification<E, S>) => {
const result: unknown = {}
return result as unknown as InteractorType<E, typeof specification>;
}
}
InteractorSpecification
類型查找名為 locators 的屬性,該屬性具有動態且由用戶添加的字段
export type InteractorSpecification<E extends Element, S extends InteractorSpecification<E, S>> =
{
selector?: string;
locators: Record<keyof S['locators'], LocatorFn<E>>;
};
高階函數返回此類型
return result as unknown as InteractorType<E, typeof specification>;
InteractorType
看起來像這樣:
export type LocatorImplementation<E extends Element, S extends InteractorSpecification<E, S>> =
{[K in keyof S['locators']]: (value: string) => InteractorInstance<E, S>}
export type InteractorType<E extends Element, S extends InteractorSpecification<E, S>> = LocatorImplementation<E, S>;
基本上它從原始specification
的location
獲取屬性並將它們映射到返回類型。
不會推斷高階函數的類型參數,但如果我傳入一個顯式類型參數,它會起作用:
const Link = createInteractor<HTMLLinkElement>('link')({
selector: 'a',
locators: {
byThis: (element) => element.href,
byThat: (element) => element.title
},
});
// type is not inferred
// const Link: LocatorImplementation<HTMLLinkElement, InteractorSpecification<HTMLLinkElement, InteractorSpecification<HTMLLinkElement, unknown>>>
Link.byThat('foo');
Link.byThis('bar')
const spec = {
selector: 'a',
locators: {
byThis: (element: HTMLLinkElement) => element.href,
byThat: (element: HTMLLinkElement) => element.title
},
asfsdfsd: 'I also need to catch unknown props'
}
// works with explicit type
const F = createInteractor<HTMLLinkElement>('link')<typeof spec>(spec)
F.byThat('foo');
F.byThis('bar');
推理失敗源於createInteractor
並且是泛型使用不當的結果。
function createInteractor<E extends Element>(name: string) {
return <S extends InteractorSpecification<E, S>>(specification: InteractorSpecification<E, S>) => {
const result: unknown = {}
return result as unknown as InteractorType<E, typeof specification>;
}
}
內部函數聲明S
將其類型與外部函數中的E
類型相關聯。 這是正確且可取的,但問題是S
沒有用於可以從中推斷出的上下文中。
為了解決這個問題,我們將內部函數更改為
<S extends InteractorSpecification<E, S>>(specification: S) => {...}
這會強制執行相同的約束,但將S
與值specification
相關聯,這樣 S 是從調用站點為specification
傳遞的參數推斷出來的,關鍵的是,其約束中的S
extends InteractorSpecification<E, S>
也是從該參數推斷出來的,最終確定locator
和定位器類型。
雖然這一更改足以解決您遇到的特定問題,但createInteractor
泛型再次引起了相關問題,但這次是在類型參數E
。
每當您有一個類型參數不涉及任何函數參數的類型時,您應該進行雙重檢查。 它通常是偽裝成類型參數的類型斷言。
在我們的特定情況下,它允許我們編寫代碼,如
const F = createInteractor<HTMLLinkElement>('input')(spec)
即使 spec 應該用於HtmlLinkElement
s。
TypeScript 的內置 DOM 類型,在lib.dom.d.ts
,提供了一個名為HtmlElementTagNameMap
的有用類型,它將標簽名稱映射到它們相應的元素類型(這就是document.querySelector('input')?.value
typechecks 的方式)。
我們將使用這些信息來防止此類無效調用,同時通過獲取特定標簽名稱並使用它來確定和傳播元素類型來進一步改善消費體驗。
function createInteractor<N extends keyof HTMLElementTagNameMap>(name: N) {
type Tag = HTMLElementTagNameMap[N];
return <S extends InteractorSpecification<Tag, S>>(specification: S) => {
return {} as InteractorType<Tag, S>;
}
}
因此我們現在可以寫
const Link = createInteractor('link')({
selector: 'a',
locators: {
byThis: (element) => element.href,
byThat: (element) => element.title
},
});
// type is inferred
Link.byThat('foo');
Link.byThis('bar')
安全簡潔,而我們現在希望收到一個錯誤
const badSpec = {
selector: 'a',
locators: {
byThis: (element: HTMLInputElement) => element.value,
byThat: (element: HTMLLinkElement) => element.title
},
}
// error is correctly given for incompatible locator
const bad = createInteractor('link')(badSpec);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.