3

我正在为如何使用 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;
}

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

const values = getDataFromProviders({
    ten: { getData: () => 10 },
    greet: { getData: () => 'hi' }
});

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

{
    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我认为我做不到。

任何帮助将不胜感激。

4

1 回答 1

7

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

interface TValues {
    ten: number;
    greet: string;
}

请注意,您实际上不必定义此 type,只需想象它存在,并将其用作泛型参数 named 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' }
});

神奇地(事实上,使用映射类型的推断,正如@Aris2World 指出的那样),打字稿能够推断出正确的类型:

let n: number = values.ten;
let s: string = values.greet;

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

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

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

//no error, "const values: {}" is inferred for values

有一种方法可以让 typescript 更早地检测到这个错误,但会增加DataProviders类型定义的复杂性:

type DataProviders<TValues> = 
    {[name in keyof TValues]: DataProvider<TValues[name]>}
   & { [name: string]: DataProvider<{}> };

与可索引类型的交集增加了一个要求,即 的每个属性都DataProviders必须与 兼容DataProvider<{}>。它使用空对象类型{}作为通用参数,DataProvider因为DataProvider它具有对任何数据类型都兼容的好属性T- DataProvider<T>DataProvider<{}>T返回类型getData(),并且任何类型都与空对象类型兼容{}

于 2017-06-11T06:02:26.787 回答