我將如何正確撰寫此介面方法以使其具有通用性。請注意,我不想使整個介面通用,并且T可以是擴展Record<string, string>的或另一個介面。下面的最小示例。 Record<string, string>
interface IFoo {
source: <T extends Record<string, string>>(table: string, calcEngine?: string, tab?: string, predicate?: (row: T) => boolean) => Array<T>;
}
const getFooRows = function <T extends Record<string, string>>(table: string, calcEngine?: string, tab?: string) {
var row1: Record<string, string> = {};
var row2: Record<string, string> = {};
return [row1, row2] as Array<T>;
}
const bar: IFoo = {
source(table, calcEngine, tab, predicate) {
return predicate
? getFooRows(table, calcEngine, tab).filter(r => predicate!(r))
: getFooRows(table, calcEngine, tab);
}
}
使用此代碼,我希望執行以下操作:
interface ICustomRow extends Record<string, string> {
customProp1: string;
customProp2: string;
}
const genericRows = bar.source("genericTable");
genericRows.forEach( r => {
// r is just Record<string, string>, no intellisene on r
console.log(`${r["dynamicProp1"]} ${r["dynamicProp2"]}`);
});
const customRows = bar.source<ICustomRow>("customTable");
customRows.forEach( r => {
// r should be a ICustomRow
// Intellisense should should 'customProp1/2' when I type 'r.'
// Should also be able to access r["dynamicProp1"] without compile error as well
console.log(`${r.customProp1} ${r.customProp2}`);
});
我知道呼叫者可以傳入一個型別,T該型別與由 標識的表中保存的資料不對應table,但這將作為要求記錄在案。這主要是為了在我的整個打字稿專案中進行“內部”呼叫,以使我免于總是做 a .source() as Array<ICustomRow>,這只是一種偏好.source<ICustomRow>()。“對我來說”讀起來更好。
但是,使用此代碼,我得到以下編譯錯誤:
錯誤 TS2322 (TS)
型別 '<T extends Record<string, string>>(table: string, calcEngine: string | undefined, tab: string | undefined, predicate: ((row: T) => boolean) | undefined) => Record<string, string>[]' 不能分配給型別 '<T extends Record<string, string>>(table: string, calcEngine?: string | undefined, tab?: string | undefined, predicate?: ((row: T) = > boolean) | undefined) => T[]'。
型別 'Record<string, string>[]' 不可分配給型別 'T[]'。
型別 'Record<string, string>' 不可分配給型別 'T'。
'Record<string, string>' 可分配給型別'T' 的約束,但'T' 可以用約束'Record<string, string>' 的不同子型別來實體化。
uj5u.com熱心網友回復:
問題是它在型別引數中getFooRows()是通用T的,但其呼叫簽名中的任何引數實際上都不依賴于該型別引數,因此編譯器無法推斷它與T您的source(). (這表明存在型別安全問題,但正如您在問題中提到的那樣,您知道這一點,我不會在這里強調這一點。)
處理這個問題的方法是從物件字面量T的呼叫簽名中獲取泛型型別引數,并在對. 不幸的是,因為您沒有注釋方法引數并且依賴于背景關系型別,所以您在范圍內沒有該型別的名稱。source()getFooRows()
我們可以決定像這樣顯式地注釋所有內容:
const bar: IFoo = {
source<T extends Record<string, string>>(
table: string, calcEngine?: string,
tab?: string, predicate?: (row: T) => boolean
) {
return predicate
? getFooRows<T>(table, calcEngine, tab).filter(r => predicate(r))
: getFooRows<T>(table, calcEngine, tab);
}
}
編譯得很好,但讓我們看看如何避免這種情況。
如果你有一個t型別的引數T,你可以寫typeof t. 但你沒有。事實上,你唯一有一點關系的引數T是predicate,它的型別是((row: T) => boolean) | undefined。(請注意,這是一個可選引數,這意味著它很容易在source()沒有任何依賴于T.而不是做這個括號內的免責宣告。)
因此,要從 中提取T,typeof predicate首先我們必須擺脫與 的聯合undefined。有一個提供的NonNullable<T>實用程式型別可以從聯合中洗掉null和undefined,所以我們可以從 開始NonNullable<typeof predicate>,然后是(row: T) => boolean。
現在我們有了一個函式型別,它的引數就是我們想要的。有一個提供的Parameters<T>實用程式型別可以獲取函式引數型別串列的元組。所以我們可以使用Parameters<NonNullable<typeof predicate>>,也就是[row: T]。
最后我們有一個元組一對一的元素,我們想要那個元素型別。我們可以通過使用索引對其進行索引0來獲取它T。這給了我們:
const bar: IFoo = {
source(table, calcEngine?, tab?, predicate?) {
type T = Parameters<NonNullable<typeof predicate>>[0]
return predicate
? getFooRows<T>(table, calcEngine, tab).filter(r => predicate(r))
: getFooRows<T>(table, calcEngine, tab);
}
}
這也編譯得很好。
請注意,我們不必使用實用程式型別;相反,我們可以使用條件型別推斷來更直接地提取相關型別,如下所示:
const bar: IFoo = {
source(table, calcEngine?, tab?, predicate?) {
type T = typeof predicate extends
((row: infer T) => boolean) | undefined ? T : never;
return predicate
? getFooRows<T>(table, calcEngine, tab).filter(r => predicate(r))
: getFooRows<T>(table, calcEngine, tab);
}
}
這也有效。
因此,您可以使用三種方法來處理T要傳入的型別以修復錯誤。哦,還有四,如果你只想承認這里沒有型別安全保證,你不妨傳入型別any并繼續:
const bar: IFoo = {
source(table, calcEngine?, tab?, predicate?) {
return predicate
? getFooRows<any>(table, calcEngine, tab).filter(r => predicate(r))
: getFooRows<any>(table, calcEngine, tab);
}
}
Playground 代碼鏈接
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/514939.html
標籤:打字稿仿制药界面
