【问题标题】:Typescript, Create string based on type of a templated function打字稿,根据模板函数的类型创建字符串
【发布时间】:2021-11-06 09:51:50
【问题描述】:

我有类似下面的代码:

// this calls a rest api endpoint
function getValue<T>(endPoint: string): T {}
// example of use
const myvalue = getValue<FishToEat>("/fishes");

我希望能够删除 endPoint 参数并从以下内容中获取此字符串:

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 版本,我无法显示最终结果,以澄清我需要的从类型到端点的映射。该类型在一个泛型函数中,我们将需要它的强值。 将您的版本编辑为我需要的更多内容,但无法正常工作,here

【问题讨论】:

  • 我没有看到pathName 参数;你的意思是endPoint?您要删除哪个参数? “模板函数”大概是指“泛型函数”(我猜 JSdoc 可能在这里使用“模板”,但 TypeScript 有泛型,而不是模板元编程)。此外,如果您要声明FishToEat 之类的类型并确保"fishes""/fishes" 是......相同,那将是一个minimal reproducible example,这将是很好的?编程相关?
  • this 是您想要的吗?如果是这样,我可以写一个答案;如果不是,请在问题中详细说明
  • const endpoint = typeof getValue; 的意义何在?那将是字符串"function",它没有用。我刚刚将我的代码示例更改为 this 以反映您问题的新版本。对你起作用吗?还是不行?
  • 理想情况下,我希望 const endpoint = typeof getValue; 返回 FishToEat 或 WineToDrink 或我调用该函数的任何类型。

标签: typescript typescript-generics


【解决方案1】:

为了便于讨论,我将定义两种不同的对象类型和两种不同的查询端点:

interface FishToEat {
    count: number,
    fishType: string,
}
// endpoint is "/fishes"

interface WineToDrink {
    vintner: string;
    vintage: number;
}
// endpoint is "/wines"

TypeScript 中的对象类型如FishToEat 仅存在于 TypeScript 代码中;当您的代码编译为 JavaScript 时,它们完全是 erased。在运行时,将不存在FishToEat 的痕迹。所以没有希望编写像这样的 TypeScript 代码

const fish = getValue<FishToEat>(); 
const wine = getValue<WineToDrink>();

并让fishwine 在运行时成为具有不同属性的不同对象。以上将编译为 JavaScript 代码,如

const fish = getValue();
const wine = getValue();

而且我认为我们可以同意,无论您如何尝试实现 getValue(),它都不会神奇地知道要查询哪个端点。


与其尝试那样做,不如想出一些符合您需要的惯用 JavaScript 代码,然后在 TypeScript 中给它键入以帮助您使用它。由于您必须将 something 传递给 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 assertion,我们要求编译器将typeNameToEndpoint 视为从键名到字符串literal types"/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中的generic对应typeNameToEndpoint对象的keys,作为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

太好了。编译器知道fishFishToEat,而wineWineToDrink


这是基本方法。您可能不满意,接口名称FishToEatWineToDrink 与作为typeName 参数"FishToEat""WineToDrink" 传递的值之间存在冗余。这在技术上并不是多余的,因为 TypeScript 的类型系统在运行前被完全擦除,并且无论如何都不是 nominal type system,所以事实上你在 TypeScript 代码中写出了字符 ⒻⓘⓢⓗⓉⓞⒺⓐⓣ因为类型的名称和字符串的值更多的是巧合而不是冗余。

不过,也许您想尝试消除其中之一。好吧,你不能消除运行时字符串。你能做的最好的就是消除类型名称:

interface EndpointToTypeMap {
    "/fishes": {
        count: number,
        fishType: string,
    };
    "/wines": {
        vintner: string;
        vintage: number;
    };
}

现在没有名为 FishToEatWineToDrink 的类型,当您调用 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 link to code

【讨论】:

  • 真的很详细,把我原问题的所有空白都填了,非常感谢。
【解决方案2】:

这似乎是不可能的,无法从其中获取泛型函数的类型。 re here 所以无法获取类型字符串以映射到获取端点。

【讨论】:

  • 如果您试图在运行时获取类型信息,那是不可能的。如果您尝试强输入我们的getValue() 函数,以便它知道"/fishes" 映射到FishToEat,那么它绝对 可能的。听起来您希望您可以调用getValue&lt;FishToEat&gt;(),但这不起作用,因为它在运行时编译为getValue()。但是你可以调用getValue("/fishes"),编译器就会知道FishToEat会出来。您甚至可以执行this 之类的操作,这样您就可以致电getValue("FishToEat")。这对你有用吗?
  • jcalz,看起来我可以这样使用它。如果您可以发布,我会尝试接受它作为答案。顺便说一句,非常感谢。
猜你喜欢
  • 1970-01-01
  • 2020-06-12
  • 2019-11-25
  • 1970-01-01
  • 1970-01-01
  • 2020-06-13
  • 1970-01-01
  • 2013-05-09
  • 1970-01-01
相关资源
最近更新 更多