TypeScript泛型 [英] TypeScript Generics

查看:90
本文介绍了TypeScript泛型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力使用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具有不错的属性,对于任何数据类型TDataProvider<T>DataProvider<{}>兼容-TgetData(),并且任何类型都与空对象类型{}兼容.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆