想象一下,您有一个类型将提供程序名称映射到提供程序返回的数据类型。像这样的东西:
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' }
});
神奇地(事实上,使用从映射类型推断 https://www.typescriptlang.org/docs/handbook/advanced-types.html#inference-from-mapped-types正如 @Aris2World 指出的那样),typescript 能够推断出正确的类型:
let n: number = values.ten;
let s: string = values.greet;
update: 正如问题作者所指出的,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<{}> };
与 的交集可转位型 https://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types添加了一个要求,即每个属性DataProviders
必须兼容DataProvider<{}>
。它使用空对象类型{}
作为一般论点DataProvider
因为DataProvider
具有适用于任何数据类型的良好属性T
, DataProvider<T>
兼容于DataProvider<{}>
- T
是返回类型getData()
,并且任何类型都与空对象类型兼容{}
.