Generics 类型脚本泛型
我正在努力学习如何使用TypeScript强大地键入一些功能 本质上,我有一个函数,它接受数据提供者的键/值映射,并返回从每个提供者返回的数据的键/值映射。下面是问题的简化版本:Generics 类型脚本泛型,generics,typescript,mapped-types,Generics,Typescript,Mapped Types,我正在努力学习如何使用TypeScript强大地键入一些功能 本质上,我有一个函数,它接受数据提供者的键/值映射,并返回从每个提供者返回的数据的键/值映射。下面是问题的简化版本: interface DataProvider<TData> { getData(): TData; } interface DataProviders { [name: string]: DataProvider<any>; } function getDataFromPro
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;
}
…然后,值
将隐式强类型化为:
{
ten: number;
greet: string;
}
我想这可能需要返回一个泛型类型,泛型参数为TDataProviders
,但我不能完全解决这个问题
这是我能想到的最好的,但不编译
type DataFromDataProvider<TDataProvider extends DataProvider<TData>> = TData;
type DataFromDataProviders<TDataProviders extends DataProviders> = {
[K in keyof TDataProviders]: DataFromDataProvider<TDataProviders[K]>;
}
类型DataFromDataProvider=TData;
类型DataFromDataProviders={
[K in-keyof-TDataProviders]:DataFromDataProvider;
}
我正在努力设计一个DataFromDataProvider
类型,该类型在编译时不需要将TData
作为第二个参数显式传递,我想我做不到这一点
任何帮助都将不胜感激。假设您有一个类型,该类型将提供程序名称映射到提供程序返回的数据类型。大概是这样的:
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' }
});
更新:正如问题作者所指出的,上面代码中的getDataFromProviders
并没有真正检查它接收的对象的每个属性是否符合DataProvider
接口
例如,如果getData
拼写错误,则不会出现错误,只会将空对象类型推断为getDataFromProviders
的返回类型(因此,当您尝试访问结果时,仍然会出现错误)
有一种方法可以使typescript更早地检测到此错误,但代价是在数据提供程序中增加复杂性
类型定义:
type DataProviders<TValues> =
{[name in keyof TValues]: DataProvider<TValues[name]>}
& { [name: string]: DataProvider<{}> };
类型数据提供程序=
{[name in keyof TValues]:数据提供程序}
&{[名称:字符串]:数据提供程序};
与的交集增加了一项要求,即
数据提供程序
的每个属性必须与数据提供程序
兼容。它使用空对象类型{}
作为DataProvider
的泛型参数,因为DataProvider
具有一个很好的属性,即对于任何数据类型T
,DataProvider
与DataProvider
-T
都是getData()的返回类型,如果提供程序的数量是固定的且相对较少,那么任何类型都可以与空对象类型兼容(感谢@EvanSebastian,这是一种有趣的方法。我不知道您可以编写[K in K2]
其中K2不是数组。生成的类型正是我想要的,但代码不起作用。这种方法会丢失数据提供程序的物理名称。我可以让每个提供程序返回它们的名称,但我想让它保持干燥。哦,我只是复制粘贴了你的代码,你显然需要修改它。我应该离开实现空,我的错。是的,很遗憾,不可能让每个提供程序都返回它们的类型并从中获取字符串文本类型。我相信您需要存在类型,不用担心。我正在考虑[K1 in K2]位,更多地实现了<代码> K2不是一个数组,而是一个字符串值的结合,一个字符串可以被认为是集合中只有11个值的联合。现在就更有意义了。这是一个非常好的解决方案。我认为这是映射类型的一个很好的高级例子。我认为如果你可以替换的话,你的文章将是完美的。神奇的是,在没有泛型的情况下,类型推断在文档中有一个解释或引用。不幸的是,关于的类型推断只有一小段,并且没有提到它的限制。神奇之处在于在不受文档限制的情况下得到您想要的东西-如果您对编译器施加过大的压力,它会开始推断空对象类型{}
,甚至更糟的是,任何,因此如果您依赖类型推断,那么--noImplicitAny
是必须的。谢谢@artem,做得不错。我注意到只有一个缺陷——编译器似乎没有严格强制getDataFromProviders
的使用者以{[name:string]:DataProvider}
的形式传递参数。因此,您可以传入拼写错误,例如{ten:{getDatam:()=>10}}
,然后它进行编译,只有尝试使用类似result.ten的结果时才会出错,因为TS认为结果现在是{}
。如果编译器在使用错误的参数进行调用时出错,那就太好了!有一种方法可以更早地检测拼写错误,但不确定是否值得增加复杂性。我更新了答案。
let n: number = values.ten;
let s: string = values.greet;
const values = getDataFromProviders({ ten: { getDatam: () => 10 } });
//no error, "const values: {}" is inferred for values
type DataProviders<TValues> =
{[name in keyof TValues]: DataProvider<TValues[name]>}
& { [name: string]: DataProvider<{}> };