2017-06-05 10 views
3

TypeScriptでいくつかの機能を強く入力する方法に苦労しています。TypeScript Generics

私は、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になります私はこれがTDataProviders Bの一般的なパラメータでジェネリック型を返す伴うだろうと想像

{ 
    ten: number; 
    greet: string; 
} 

:暗黙的に強く型付けされたとして私はそれをうまく解決できません。

これは、私が思い付くことができる最高のですが、コンパイルされません...

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

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

私は私が明示的に2番目のパラメータとしてTDataに渡さずにコンパイルDataFromDataProviderタイプを考え出す苦労しています私は私ができるとは思わない。

ご協力いただければ幸いです。

+0

プロバイダの数が固定され、比較的小さい場合(<= 10)、私が思いつくことができる最も良いことは、可変データ型引数を使用するようにgetDataFromProvidersを変更し、ファントム型を使用してキー( "ten"、 "greet" )。ボイラープレートはたくさんありますが、あなたが望むものを提供します:https://gist.github.com/evansb/7afc5ac7e640a06759276f456970e857 –

+0

ありがとう@EvanSebastian、それは興味深いアプローチです。私はあなたがK2が配列以外の何かであるところで '[K in K2]'を書くことができるかどうか分かりませんでした。結果の型はまさに私が望むものですが、コードは機能しません。このアプローチは、データプロバイダの物理的な名前を失います。私は各プロバイダに名前を返させることができますが、私はそれをDRYにしておきたいと思います。 – 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' } 
}); 

魔法(Aris2Worldが指摘@として実際には、inference from mapped typesを使用して)、typescriptですが、正しい種類を推測することができます:

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型定義で追加の複雑さを犠牲にして、前にこのエラーを検出するためのtypescriptですを作るための方法があります:

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

indexable typeとの交点がそのDataProvidersのすべてのプロパティの要件を追加しますDataProvider<{}>と互換性がなければなりません。TgetData()の戻り値の型であり、任意のタイプは、空のオブジェクト型{}と互換性があります - DataProviderは、任意のデータ型Tため、DataProvider<T>DataProvider<{}>と互換性のある素敵なプロパティがいるので、それはDataProviderのための一般的な引数として空のオブジェクトタイプ{}を使用しています。

+0

これは本当にすっきりした解決策です! –

+0

これは、マップされた型の優れた例です。 – Aris2World

+0

ジェネリックスが存在しない場合の型推論は、[ハンドブック](https:// www)で比較的よく文書化されていますが、あなたの投稿は完璧になると思います。 typescriptlang.org/docs/handbook/type-in​​ference.html)。残念ながら、[ここでのジェネリックス](https://www.typescriptlang.org/docs/handbook/generics.html)の型推論についての短い段落は1つしかなく、その制限についての言及はありません。魔法は、文書化されていない制限内にとどまっている間にあなたが望むものを得ることにあります。コンパイラをあまりにも強く押すと、空のオブジェクトタイプ '{}'またはそれより悪い 'any'を推論し始めます。したがって、' --noImplicitAny'は、型の推論。 – artem