TypeScript泛型 [英] TypeScript Generics
问题描述
我正在努力使用TypeScript强烈键入某些功能.
I'm struggling with how to strongly type some functionality with TypeScript.
基本上,我有一个函数,该函数接受DataProvider的键/值映射并返回从每个提供者返回的数据的键/值映射.这是问题的简化版本:
Essentially I have a function that accepts a key/value map of DataProviders and returns a key/value map of the data returned from each. Here's a simplified version of the problem:
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;
}
当前getDataFromProviders
的返回类型为any
,但是我想要它,以便像这样调用...
Currently getDataFromProviders
has a return type of any
but I want it so that if called like so...
const values = getDataFromProviders({
ten: { getData: () => 10 },
greet: { getData: () => 'hi' }
});
...然后values
将隐式强类型为:
...then values
will be implicitly strongly typed as:
{
ten: number;
greet: string;
}
我想这会涉及返回一个泛型类型,该泛型类型的泛型参数为TDataProviders
,但我无法完全解决.
I imagine this would involve returning a generic type with a generic parameter of TDataProviders
but I can't quite work it out.
这是我能想到的最好的方法,但是无法编译...
This is the best I can come up with but doesn't compile...
type DataFromDataProvider<TDataProvider extends DataProvider<TData>> = TData;
type DataFromDataProviders<TDataProviders extends DataProviders> = {
[K in keyof TDataProviders]: DataFromDataProvider<TDataProviders[K]>;
}
我正在努力提出一种DataFromDataProvider
类型的类型,该类型在没有我显式传入TData
作为第二个参数的情况下进行编译,我认为我做不到.
I'm struggling coming up with a DataFromDataProvider
type that compiles without me passing in TData
explicitly as a second parameter, which I don't think I can do.
任何帮助将不胜感激.
推荐答案
想象一下,您有一个将提供程序名称映射到提供程序返回的数据类型的类型.像这样:
Imagine that you have a type that maps provider name to the data type returned by the provider. Something like this:
interface TValues {
ten: number;
greet: string;
}
请注意,您实际上不必定义此类型,只需想象它存在,然后将其用作泛型参数,命名为TValues
,随处可见:
Note that you don't actually have to define this type, just imagine it exists, and use it as generic parameter, named TValues
, everywhere:
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指出,从映射的类型),打字稿能够推断出正确的类型:
magically (in fact, using inference from mapped types, as @Aris2World pointed out), typescript is able to infer correct types:
let n: number = values.ten;
let s: string = values.greet;
更新:如问题作者所指出的那样,以上代码中的getDataFromProviders
并没有真正检查其接收到的对象的每个属性是否符合DataProvider
接口.
update: as pointed out by the author of the question, getDataFromProviders
in the code above does not really check that each property of the object it receives conforms to DataProvider
interface.
例如,如果getData
拼写错误,则没有错误,只是空对象类型被推断为getDataFromProviders
的返回类型(因此,当您尝试访问结果时,仍然会出现错误).
For example, if getData
is misspelled, there is no error, just empty object type is inferred as return type of getDataFromProviders
(so you will still get an error when you try to access the result, however).
const values = getDataFromProviders({ ten: { getDatam: () => 10 } });
//no error, "const values: {}" is inferred for values
有一种方法可以使打字稿更早地检测到此错误,但以DataProviders
类型定义中的额外复杂性为代价:
There is a way to make typescript to detect this error earlier, at the expense of additional complexity in DataProviders
type definition:
type DataProviders<TValues> =
{[name in keyof TValues]: DataProvider<TValues[name]>}
& { [name: string]: DataProvider<{}> };
与可索引类型的交集增加了一项要求,即DataProviders
的每个属性都必须与DataProvider<{}>
兼容.它使用空对象类型{}
作为DataProvider
的通用参数,因为DataProvider
具有不错的属性,对于任何数据类型T
,DataProvider<T>
与DataProvider<{}>
兼容-T
是getData()
,并且任何类型都与空对象类型{}
兼容.
The intersection with indexable type adds a requirement that every property of DataProviders
must be compatible with DataProvider<{}>
. It uses empty object type {}
as generic argument for DataProvider
because DataProvider
has the nice property that for any data type T
, DataProvider<T>
is compatible with DataProvider<{}>
- T
is the return type of getData()
, and any type is compatible with empty object type {}
.
这篇关于TypeScript泛型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!