TypeScript Generics

Cod*_*ats 3 generics typescript mapped-types

我正在努力解决如何使用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;
}
Run Code Online (Sandbox Code Playgroud)

目前getDataFromProviders有一个返回类型,any但我想要它,所以如果这样调用...

const values = getDataFromProviders({
    ten: { getData: () => 10 },
    greet: { getData: () => 'hi' }
});
Run Code Online (Sandbox Code Playgroud)

...然后values将隐式强类型为:

{
    ten: number;
    greet: string;
}
Run Code Online (Sandbox Code Playgroud)

我想这将涉及返回一个泛型类型与泛型参数,TDataProviders但我不能完全解决它.

这是我能想到的最好但不编译的......

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

type DataFromDataProviders<TDataProviders extends DataProviders> = {
    [K in keyof TDataProviders]: DataFromDataProvider<TDataProviders[K]>;
}
Run Code Online (Sandbox Code Playgroud)

我正在努力想出一个DataFromDataProvider没有我TData明确作为第二个参数进行编译的类型,我认为我无法做到.

任何帮助将不胜感激.

art*_*tem 6

想象一下,您有一个类型,将提供程序名称映射到提供程序返回的数据类型.像这样的东西:

interface TValues {
    ten: number;
    greet: string;
}
Run Code Online (Sandbox Code Playgroud)

请注意,您实际上不必定义此类型,只是想象它存在,并将其用作通用参数,名称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' }
});
Run Code Online (Sandbox Code Playgroud)

神奇地(实际上,使用来自映射类型的推断,如@ Aris2World所指出的),typescript能够推断出正确的类型:

let n: number = values.ten;
let s: string = values.greet;
Run Code Online (Sandbox Code Playgroud)

更新:正如问题的作者所指出的,getDataFromProviders在上面的代码中并没有真正检查它接收的对象的每个属性是否符合DataProvider接口.

例如,如果getData拼写错误,则没有错误,只将空对象类型推断为返回类型getDataFromProviders(因此,当您尝试访问结果时仍会出现错误).

const values = getDataFromProviders({ ten: { getDatam: () => 10 } });

//no error, "const values: {}" is inferred for values
Run Code Online (Sandbox Code Playgroud)

有一种方法可以让typescript更早地检测到这个错误,但代价是DataProviders类型定义的额外复杂性:

type DataProviders<TValues> = 
    {[name in keyof TValues]: DataProvider<TValues[name]>}
   & { [name: string]: DataProvider<{}> };
Run Code Online (Sandbox Code Playgroud)

具有可索引类型的交集添加了DataProviders必须与每个属性兼容的要求DataProvider<{}>.它使用空对象类型{}为通用参数DataProvider,因为DataProvider有很好的特性,对于任何数据类型T, DataProvider<T>可以兼容DataProvider<{}>- T是的返回类型getData(),以及任何类型与空对象类型兼容{}.