2017-06-05 70 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