泛型的Typescript方法重载

时间:2018-07-24 22:43:58

标签: typescript overloading typescript-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 个答案:

答案 0 :(得分:1)

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

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

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

然后,getSettinTwo()至少使用TKey,因为它是key参数的类型。但是现在,TValue仅用于返回值。由于 caller 指定了类型参数的值(或者从 caller 传递的值中推论得出),因此该方法签名表示类似“我将返回任何键入呼叫者想要的”。确实不可能正确实施。只有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,即使其中最多一个是正确的。记住type system is erased at runtime,所以两个调用都被转换为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编写),然后尝试为其添加类型。或者,如果不是这样,则上述键入之一可能对您有用。无论如何,祝你好运!

答案 1 :(得分:-3)

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

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

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