2017-06-05 72 views
3

我正在努力如何用TypeScript強類型化一些功能。TypeScript泛型

本質上,我有一個函數接受DataProviders的鍵/值映射,並返回從每個返回的數據的鍵/值映射。這裏的問題的一個簡化版本:

interface DataProvider<TData> { 
    getData(): TData; 
} 

interface DataProviders { 
    [name: string]: DataProvider<any>; 
} 

function getDataFromProviders<TDataProviders extends DataProviders>(
    providers: TDataProviders): any { 

    const result = {}; 

    for (const name of Object.getOwnPropertyNames(providers)) { 
     result[name] = providers[name].getData(); 
    } 

    return result; 
} 

目前getDataFromProvidersany返回類型,但我想它,這樣如果調用是這樣的...

const values = getDataFromProviders({ 
    ten: { getData:() => 10 }, 
    greet: { getData:() => 'hi' } 
}); 

...然後values會隱式強類型爲:

{ 
    ten: number; 
    greet: string; 
} 

我想這將涉及與TDataProviders b的泛型參數返回一個泛型類型我不能解決這個問題。

這是最好的我能想出但不編譯...

type DataFromDataProvider<TDataProvider extends DataProvider<TData>> = TData; 

type DataFromDataProviders<TDataProviders extends DataProviders> = { 
    [K in keyof TDataProviders]: DataFromDataProvider<TDataProviders[K]>; 
} 

我竭力想出,沒有我在TData明確地傳遞作爲第二個參數編譯一個DataFromDataProvider類型,我認爲我不能這樣做。

任何幫助將不勝感激。

+0

如果提供程序的數量是固定的並且相對較小(<= 10),則可以通過更改getDataFromProviders以使用可變參數,使用幻像類型對密鑰進行編碼(「ten」,「greet」 )。許多樣板,但它給你想要的:https://gist.github.com/evansb/7afc5ac7e640a06759276f456970e857 –

+0

謝謝@EvanSebastian,這是一個有趣的方法。我不知道你可以寫出'[K in K2]',其中K2不是數組。結果類型正是我想要的,但代碼不起作用。這種方法失去了數據提供者的物理名稱。我可以讓每個提供者返回他們的名字,但我想保持乾爽。 – CodeAndCats

+0

哦,我只是複製粘貼你的代碼,你需要明顯修改它。我應該把實施留空,我的壞處。 是的,不幸的是,不可能讓每個提供者返回它們的類型,並從中獲得一個字符串文字類型。我相信你需要存在型 –

回答

6

想象一下,您有一種將提供者名稱映射到提供者返回的數據類型的類型。事情是這樣的:

interface TValues { 
    ten: number; 
    greet: string; 
} 

請注意,您實際上沒有定義此類型,試想它的存在,並把它作爲一般的參數,命名爲TValues,無處不在:

interface DataProvider<TData> { 
    getData(): TData; 
} 

type DataProviders<TValues> = 
    {[name in keyof TValues]: DataProvider<TValues[name]>}; 


function getDataFromProviders<TValues>(
    providers: DataProviders<TValues>): TValues { 

    const result = {}; 

    for (const name of Object.getOwnPropertyNames(providers)) { 
     result[name] = providers[name].getData(); 
    } 

    return result as TValues; 
} 


const values = getDataFromProviders({ 
    ten: { getData:() => 10 }, 
    greet: { getData:() => 'hi' } 
}); 

神奇地(實際上,使用inference from mapped types,正如@Aris2World所指出的那樣),打字稿能夠推斷出正確的類型:

let n: number = values.ten; 
let s: string = values.greet; 

更新:正如問題的作者指出的,getDataFromProviders在上面的代碼中並沒有真正檢查它接收的對象的每個屬性是否符合DataProvider接口。

例如,如果getData拼寫錯誤,沒有錯誤,只是空對象類型被推斷爲返回類型getDataFromProviders(因此,當您嘗試訪問結果時仍然會出錯)。

const values = getDataFromProviders({ ten: { getDatam:() => 10 } }); 

//no error, "const values: {}" is inferred for values 

有一種方法,使打字原稿此錯誤早些時候,在額外的複雜性爲代價檢測DataProviders類型定義:

type DataProviders<TValues> = 
    {[name in keyof TValues]: DataProvider<TValues[name]>} 
    & { [name: string]: DataProvider<{}> }; 

indexable type交集增加了一個要求,即DataProviders每個屬性必須與DataProvider<{}>兼容。它使用空對象類型{}DataProvider作爲一般的參數,因爲DataProvider具有很好的特性,對於任何數據類型TDataProvider<T>是兼容DataProvider<{}> - TgetData()返回類型,以及任何類型與空對象類型{}兼容。

+0

這是一個非常整潔的解決方案! –

+0

我認爲這是映射類型的一個很好的高級示例。我認爲,如果您可以用神奇的方式替代對文檔的解釋或參考,那麼您的文章將是完美的) – Aris2World

+0

在[手冊](https:// www。 typescriptlang.org/docs/handbook/type-in​​ference.html)。不幸的是,關於[泛型]的類型推斷只有一小段(https://www.typescriptlang.org/docs/handbook/generics.html),並且沒有提及其限制。如果你太努力地推動編譯器,它會開始推斷空的對象類型'{}',甚至更糟糕的是'any',所以'--noImplicitAny'是必須的,如果你依賴的話類型推斷。 – artem

相關問題