繁体   English   中英

泛型的Typescript方法重载

[英]Typescript method overload with generics

我搜索了一些文档和博客,但是没有人涵盖此示例。

export interface IConfigurationProvider {
    getSetting<TKey extends ConfigNames>(key: TKey): string;
    getSetting<TKey extends ConfigNames, TValue extends object>(key: TKey): TValue;
}

export enum ConfigNames {
    SomeSetting,
    AnotherSetting
}

export class ConfigurationProvider implements IConfigurationProvider {

    public getSetting<TKey extends ConfigNames>(key: TKey): string;
    public getSetting<TKey extends ConfigNames, TValue extends object>(key: TKey): TValue;
    public getSetting(key: any): any {
        // what the heck do I do here???
    }
}

因此,在大多数重载示例中,人们都会做类似的事情:

public getHero(name: string);
public getHero(name: string, skill: string);
public getHero(name: string, skill?: string): Hero {
    if (!skill) {
        return this.heroes.find((hero: Hero) => hero.name === name);
    }
    return this.heroes.find((hero: Hero) => hero.name === name && hero.skill === skill);
}

但是在我的情况下,唯一的区别是返回类型,该类型由提供的泛型类型定义指定。 那么,如果我没有访问TValue权限,我将如何实现可以处理两种情况的基本实现? 在C#中, TValue是一个可操作的项目,但是由于Typescript确实超载,所以我不能这样做: if (typeof TValue === undefined)

如果无法实现,那很好,但是代码可以编译,如果我确实这样做,则let configItem = config.getSetting<ConfigNames, Date>(ConfigNames.SomeSetting); 智能感知正确地推断出我应该找回Date对象。 因此,像C#一样,有些东西可以编译,但是在运行时不起作用,因此希望这不是其中一种情况。 因此,如果有人知道如何在此处正确实现基本方法,我想知道。

更新:对于那些要求解释我在此用例背后的意图的人,以及对于其他阅读本说明的人...

在C#中,我将具有以下方法

public string GetSetting<T>(TKey key) where T : struct, IConvertible
{
    return CloudConfigurationManager.GetSetting(key.ToString());
}

public T GetSetting<TKey, T>(TKey key) where TKey : struct, IConvertible
{
    var settingString = CloudConfigurationManager.GetSetting(key.ToString());
    return JsonConvert.DeserializeObject<T>(settingString);
}

这允许将配置项存储为json对象,因此我可以将配置项存储为“ 11:00:00”,然后执行以下操作: var timespanSetting = config.GetSetting<ConfigNames, TimeSpan>(ConfigNames.SomeTimeSpanSetting); <-理想情况下,这就是我想要做的。 如果我必须使用不同的名称来创建2个方法,那么我会这样做,但是如果我不必这样做,那就太好了。

这两个方法签名都不是特别良好的行为。 让我们暂时搁置过载问题。 假设您将它们分为两个单独的方法:

getSettingOne<TKey extends ConfigNames>(key: ConfigNames): string;
getSettingTwo<TKey extends ConfigNames, TValue extends object>(key: TKey): TValue;

因此, getSettingOne()根本不使用TKey 这很奇怪。 无论您如何调用它,都可能将其推断为{} ,即使您在调用TKey时将TKey显式设置为某个值,也不会对其产生任何影响。 它有什么作用? 您想用这种方法做什么?

然后, getSettinTwo()至少使用TKey因为它是key参数的类型。 但是现在, TValue仅用于返回值。 由于调用者指定了类型参数的值(或者从调用者传递的值中推论得出),因此此方法签名表示类似“我将返回调用者想要的任何类型的值”之类的内容。 确实不可能正确实施。 只有never的类型是安全地分配给每一个类型的,所以,我解释getSettingTwo()为需要的方法TKey ,并抛出一个异常,或从未回报什么的。 既然那不是您的意思,那么您打算用这种方法做什么?


因此,我不知道如何分别实现每个签名(除了引发异常),我真的不知道如何将它们作为重载函数实现。 但是,如果问题只是“我如何为此实现签名”,那么答案是,实现签名至少需要与所有调用签名一样宽松。 您也可以为其指定类型参数。 因此,以下内容将进行类型检查,并“至少使您可以访问TKey ”与任一呼叫签名相同:

public getSetting<TKey extends ConfigNames>(key: ConfigNames): string;
public getSetting<TKey extends ConfigNames, TValue extends object>(key: TKey): TValue;
public getSetting<TKey extends ConfigNames, TValue extends object>(key: TKey): TValue | string {
  throw new Error(); // useless
}

但是仍然没有实现该问题的好方法(这就是为什么我只是在这里抛出错误)。 我的建议是让您重新查看方法签名并将其更改为您的实际含义。 我认为您在问题中提供的信息不足,无法让任何人帮助您做到这一点。 也许您想编辑您的问题或使用此信息创建一个新问题? 在此之前,祝您好运!


更新:

好的,现在我对您要执行的操作有所了解。 似乎您想用以下两种方式之一调用该函数:一种仅获取JSON字符串,另一种解析该JSON字符串并断言它是某种调用者指定的类型。

请注意,后面的调用根本不是类型安全的...调用方可以调用

let configItemDate = config.getSetting<ConfigNames, Date>(ConfigNames.SomeSetting);

要么

let configItemRegExp = config.getSetting<ConfigNames, RegExp>(ConfigNames.SomeSetting);

并且编译器会认为configItemDateDate ,而configItemRegExpRegExp ,即使其中最多一个是正确的。 请记住, 类型系统在运行时被擦除 ,因此两个调用都被转换为config.getSetting(ConfigNames.SomeSetting) ...,这意味着configItemDateconfigItemRegExp在运行时将是相同的值。

有时您确实想在TypeScript中执行不安全的类型声明,但是最好格外小心。 有一种方法声称可以返回调用方想要的任何类型,而又没有办法验证它比我想要的风险更大。 但是,无论如何,假设您想那样做。

用单个方法获得所需结果的唯一方法是在运行时提供某种方法来区分返回字符串的调用和返回对象的调用。 这意味着您需要引入一个新参数,该参数的值对此进行控制:

getSetting(key: ConfigNames, parseJSON?: true);

如果将parseJSON参数传递为true ,则将获得一个对象;如果不进行设置,则将获得JSON字符串。 它可以(可能)通过如下重载来完成:

public getSetting(key: ConfigNames): string;
public getSetting<TValue extends object>(key: ConfigNames, parseJSON: true): TValue;
public getSetting<TValue extends object>(key: ConfigNames, parseJSON?: true): TValue | string {
  const json = CloudConfigurationManager.GetSetting(key.toString());       
  return (parseJSON) ? JSON.parse(json) : json;
}

但是到那时,超载并没有给您带来太多好处。 更直接的是两种必须完全不同的方法,例如:

getSetting(key: ConfigNames): string;
getSettingAs<V extends object>(key: ConfigNames): V;

将单独实施。


再次重申, getSettingAs()不是类型安全的,因为调用方可以将任何内容插入V ,并且它将安抚编译器而不会在运行时影响任何内容。

举例来说,我真诚地怀疑任何普通的JSON解析器都会产生一个有效的JavaScript Date对象。 您可以创建一个自定义JSON解析器来执行此操作。 而且,如果您开始自定义JSON解析器,则会导致另一种进行getSetting()可能方式...传递一个可选的JSON解析函数,该函数已知会转换为所需的类型:

public getSetting(key: ConfigNames): string;
public getSetting<V extends object>(key: ConfigNames, jsonParser: (json: string)=>V): V;
public getSetting<V extends object>(key: ConfigNames, jsonParser?: (json: string)=>V): V | string {
  const json = CloudConfigurationManager.GetSetting(key.toString());       
  return (jsonParser) ? jsonParser(json) : json;
}

现在这是类型安全的,但是依赖于调用者传递有效的JSON解析器。


希望这些想法之一能有所帮助。 请注意,由于您似乎没有使用TKey ,因此我是如何将其全部删除的。 还要注意,您给出的enum是一个数字,但您似乎期望key可能是字符串? 如果您是我,我将专注于让您的代码在运行时正常工作(也许是用纯JavaScript编写),然后尝试为其添加类型。 或者,如果不是这样,则上述键入之一可能对您有用。 无论如何,祝你好运!

Typescript将转换为Javascript,因此Typescript中没有方法重载。

您可以像这样使用参数对象:

getHero (param:  { name: string; skill?: string; }) {
  if (!param.skill) {
    return ... 
  }
  return ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM