我搜索了一些文档和博客,但是没有人涵盖此示例。
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);
<-理想情况下,这就是我想要的能够做到。如果我必须创建两个名称不同的方法,我会这样做,但是如果不需要的话,那会很好。
答案 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);
,编译器会认为configItemDate
是Date
,而configItemRegExp
是RegExp
,即使其中最多一个是正确的。记住type system is erased at runtime,所以两个调用都被转换为config.getSetting(ConfigNames.SomeSetting)
...,这意味着configItemDate
和configItemRegExp
在运行时将是相同的值。
有时您确实想在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 ...
}