我的代码类似于下面:
// this calls a rest api endpoint
function getValue<T>(endPoint: string): T {}
// example of use
const myvalue = getValue<FishToEat>("/fishes");
我希望能够删除端点参数,并从以下内容获得此字符串:
interface TypeToEndpointMap {
FishToEat: "/fishes"
}
上面的界面显然是垃圾,但这是可能的,我怎么能做到这一点?
这是一个可以运行的版本,但不会像希望的那样工作:
interface FishToEat {
count: number,
fishType: string,
}
let TypeToEndpointMap: { [key: string]: string } = {};
TypeToEndpointMap.FishToEat = "/fishes";
// this calls a rest api
function getValue<T>(pathName: string): T {
const endpoint = typeof getValue;
console.log(endpoint);
return {} as T;
}
const myvalue = getValue<FishToEat>("/fishes");
尝试jcalz版本,我无法显示最终结果,以澄清其从类型到端点映射我需要。该类型位于一个泛型函数中,我们将需要在该泛型函数中使用强值。编辑你的版本,以更多的我需要,但不能让它工作,在这里
为了便于讨论,我将定义两个不同的对象类型和两个不同的查询端点:
interface FishToEat {
count: number,
fishType: string,
}
// endpoint is "/fishes"
interface WineToDrink {
vintner: string;
vintage: number;
}
// endpoint is "/wines"
像FishToEat
这样的TypeScript对象类型只存在于TypeScript代码中;当您的代码编译为JavaScript时,它们将被完全删除。在运行时,不存在FishToEat
的踪迹。所以没法写出像
const fish = getValue<FishToEat>();
const wine = getValue<WineToDrink>();
并使fish
和wine
在运行时成为具有不同属性的有用的不同对象。上面的代码将编译成像
const fish = getValue();
const wine = getValue();
,我认为我们可以同意,无论你如何尝试实现getValue()
,它都不会神奇地知道要查询哪个端点。
与其尝试那样做,不如想出一些习惯的JavaScript代码,让它按照你想要的方式运行,然后在TypeScript中给它类型来帮助你使用它。由于您必须将内容传递给getValue()
以使其选择端点,因此我们不妨选择最容易使用的内容。这可能只是端点路径:
// pass in endpoint path
getValue("/fishes");
getValue("/wines");
但是如果这些端点路径暴露了你不想暴露的实现细节,你可以传递一些其他更友好的字符串,实现可以将其映射到端点:
// pass in friendly query name
getValue("FishToEat");
getValue("WineToDrink");
听起来你更喜欢这种方法,所以让我们来实现它并给它一些类型。
我们需要一些方法将友好的字符串映射到端点路径,所以让我们创建一个对象来保存这个映射:
const typeNameToEndpoint = {
FishToEat: "/fishes",
WineToDrink: "/wines"
} as const;
通过使用const
断言,我们要求编译器将typeNameToEndpoint
视为从键名到字符串字面值类型"/fishes"
和"/wines"
的不变映射。如果我们不这样做,编译器会推断出{FishToEat: string, WineToDrink: string}
的类型,这是真的,但没有用;我们希望编译器知道它正在查询哪个端点,这样它就可以知道应该返回哪种类型。
这是另一个独立的纯类型级映射,因此我们可以将其定义为interface
:
interface EndpointToTypeMap {
"/fishes": FishToEat;
"/wines": WineToDrink;
}
现在getValue()
的实现看起来像这样:
function getValue<K extends keyof typeof typeNameToEndpoint>(typeName: K) {
const endpoint = typeNameToEndpoint[typeName];
console.log(endpoint);
// fetch(endpoint) or something
return {} as Promise<EndpointToTypeMap[typeof endpoint]> // need a real impl here
}
它在类型参数K
中是泛型的,对应于typeNameToEndpoint
对象的键,作为typeName
传入。该实现在该对象中查找typeName
并获得endpoint
,编译器已将其强类型设置为依赖于泛型K
。您可以运行查询,然后返回正确类型的值,该值是EndpointToTypeMap[typeof endpoint]
,也取决于K
。
哦,既然你调用rest api显然,我们想要一个Promise
我猜。让我们试一下:
const fish = await getValue("FishToEat") // logs "/fishes"
// const fish: FishToEat
const wine = await getValue("WineToDrink") // logs "/wines"
// const wine: WineToDrink
好了。编译器知道fish
是FishToEat
,wine
是WineToDrink
。
这是基本方法。您可能不满意的是,接口名称FishToEat
和WineToDrink
与作为typeName
参数"FishToEat"
和"WineToDrink"
传入的值之间存在冗余。这在技术上并不是多余的,因为TypeScript的类型系统在运行前被完全擦除,并且无论如何都不是一个名义上的类型系统,所以你在TypeScript代码中写出字符Ⓕⓘⓢ
不过,也许你想尝试消除其中一个。你不能消除运行时字符串。你能做的最好的事情就是消除类型名:
interface EndpointToTypeMap {
"/fishes": {
count: number,
fishType: string,
};
"/wines": {
vintner: string;
vintage: number;
};
}
现在没有名为FishToEat
或WineToDrink
的类型,当你调用getValue()
时,你将返回相同的强对象类型的值,但它们将是匿名的:
const fish = await getValue("FishToEat") // logs "/fishes"
/* const fish: {
count: number;
fishType: string;
} */
const wine = await getValue("WineToDrink") // logs "/wines"
/* const wine: {
vintner: string;
vintage: number;
} */
好点了吗?我对此表示怀疑;在这里,用户友好可能比DRY更好。
Playground链接到代码
这似乎是不可能的,无法从其中获得泛型函数的类型。在这里无法获取类型字符串映射到获取端点。