模板字面类型等效

发布于 2025-01-31 17:04:39 字数 847 浏览 3 评论 0 原文

我有一个像这样的枚举:

export enum ApiFunctions {
  "setHidden" = "HIDE",
  "setReadOnly" = "SET_READ_ONLY",
  "setVisible" = "SHOW",
  "setDescription" = "SET_DESCRIPTION",
  "setName" = "SET_NAME",
  "makeRequest" = "MAKE_REQUEST"
}

今天早些时候,我从此枚举中创建了一种新类型:

export type ApiActions = Exclude<`${ApiFunctions}`, "MAKE_REQUEST">

类型返回键的所有值,除了“ make_request”(set_description,....)。

此 TS 4.1和当前的Bundler的TS版本为3.9.7,由于外部提供,我无法真正对其进行更新。

我尝试通过执行以下操作来复制此类型:

export type Something = Exclude<typeof ApiFunctions[keyof typeof ApiFunctions], "MAKE_REQUEST">

但是这种类型而不是给我每个键 set_name |的实际字符串值| set_description ... apiftunctions.setname的行中给我一些东西| apiftunctions.setDescription ...

是否有一种方法可以以其他方式实现模板文字创建的类型完全相同?

I have an enum like so:

export enum ApiFunctions {
  "setHidden" = "HIDE",
  "setReadOnly" = "SET_READ_ONLY",
  "setVisible" = "SHOW",
  "setDescription" = "SET_DESCRIPTION",
  "setName" = "SET_NAME",
  "makeRequest" = "MAKE_REQUEST"
}

Earlier today I created a new type from this enum like so:

export type ApiActions = Exclude<`${ApiFunctions}`, "MAKE_REQUEST">

This type returns all the values of the keys except "MAKE_REQUEST" (SET_DESCRIPTION,....)

The problem is Template literal types were released on ts 4.1 and the current bundler's ts version is 3.9.7 and I can't really update it since it is externally provided.

I have tried replicating this type by doing:

export type Something = Exclude<typeof ApiFunctions[keyof typeof ApiFunctions], "MAKE_REQUEST">

But this type instead of giving me the actual string value of each key SET_NAME | SET_DESCRIPTION ... gives me something in the lines of ApiFunctions.setName | ApiFunctions.setDescription ...

Is there a way of achieving exactly the same type created by the template literal on any other way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

忆伤 2025-02-07 17:04:39

不,在打字稿4.1之前,没有程序化方法可以扩展 types 对相应的字符串字面类型


但是,您可能不想做这样的事情(即使使用Typescript 4.1及以上),并且可能有更好的方法。 TypeScript enum s最适合其值 opaque 到您的打字稿代码库的情况。因此,除了 enum 定义本身之外,您的代码不应关心其特定值。您应该访问这些值的唯一方法是通过枚举。因此,始终 apiftunctions.sethiddend ,从不“ hide”

这意味着您应该能够更改thexcript代码内部的值,并且不应破坏。当然,如果这些值对某些外部系统(例如API)很重要,那么与该系统的通信将不再起作用。但是相反,如果外部系统更改,以便它想要“隐藏” 而不是“ hide” ,那么您唯一必须在TS代码中更改的是 sethidden =“隐藏” ;您不需要进行“ hide” 字符串字面的全局查找,并使用“隐藏” 文字。

毕竟,如您所见,编译器并不容易参考枚举的字面价值。它的观点是, apiftunctions.sethiddend “ hide” 更具体,因此您不能使用类型“ hide” ,其中需要类型 apiftiftions.SETHIDDER 的值。与编译器与编译器作斗争是为了获得可疑利益而付出了很多努力。如果可能的话,让枚举保持枚举。

考虑到这一点,我对 apiactions 的建议将是:

export enum ApiFunctions {
  setHidden = "HIDE",
  setReadOnly = "SET_READ_ONLY",
  setVisible = "SHOW",
  setDescription = "SET_DESCRIPTION",
  setName = "SET_NAME",
  makeRequest = "MAKE_REQUEST"
}

export type ApiActions = Exclude<ApiFunctions, ApiFunctions.makeRequest>;
/* type ApiActions = ApiFunctions.setHidden | ApiFunctions.setReadOnly | 
     ApiFunctions.setVisible | ApiFunctions.setDescription | ApiFunctions.setName */

另一方面,如果您的打字稿代码真的确实关心 可能不适合您的用例。由于Typescript 3.4引入断言,获得类似枚举的对象。看起来这样:

export const ApiFunctions = {
  setHidden: "HIDE",
  setReadOnly: "SET_READ_ONLY",
  setVisible: "SHOW",
  setDescription: "SET_DESCRIPTION",
  setName: "SET_NAME",
  makeRequest: "MAKE_REQUEST"
} as const;

由此可以通过编程方式制作也称为 apiftunctions

export type ApiFunctions =
  typeof ApiFunctions[keyof typeof ApiFunctions];

这两个定义与 apiftunctions 的枚举版本相似,除了现在编译器让您通过枚举参考值作为字面类型:

export type ApiActions = Exclude<ApiFunctions, "MAKE_REQUEST">;
/* type ApiActions = "SET_NAME" | "HIDE" | "SET_READ_ONLY" | "SHOW" | "SET_DESCRIPTION" */

哪种方法(如果有的话)对您有利,取决于您的用例。我倾向于避免 Enum 在任何地方都完全避免,因为它们不属于JavaScript,并且具有令人惊讶的行为(尤其是数字枚举)。但这是个人偏爱。

Playground link to code

No, before TypeScript 4.1 there was no programmatic way to widen string enum types to their corresponding string literal types.


However, you probably shouldn't want to do such a thing (even with TypeScript 4.1 and above) and there might be a better approach. TypeScript enums are most appropriate for situations where their values are opaque to your TypeScript code base. So other than the enum definition itself, your code should not care about their specific values. The only way you should access these values is through the enum. So always ApiFunctions.setHidden and never "HIDE".

That means you should be able to change the values and nothing inside the TypeScript code should break. Of course, if those values are important to some external system like an API, then communication with that system would no longer work. But conversely, if the external system changes so that it wants "CONCEAL" instead of "HIDE", the only thing you should have to change in your TS code is setHidden = "CONCEAL"; you shouldn't need to do a global find-and-replace of the "HIDE" string literal with the "CONCEAL" literal.

After all, as you've seen, the compiler doesn't make it easy to refer to the literal values of an enum. It takes the view that ApiFunctions.setHidden is more specific than "HIDE", and so you can't use a value of type "HIDE" where a value of type ApiFunctions.setHidden is required. And fighting with the compiler to produce one from the other is a lot of effort for questionable benefit. Let enums stay enums, if at all possible.

With that in mind, my suggestion for ApiActions would be:

export enum ApiFunctions {
  setHidden = "HIDE",
  setReadOnly = "SET_READ_ONLY",
  setVisible = "SHOW",
  setDescription = "SET_DESCRIPTION",
  setName = "SET_NAME",
  makeRequest = "MAKE_REQUEST"
}

export type ApiActions = Exclude<ApiFunctions, ApiFunctions.makeRequest>;
/* type ApiActions = ApiFunctions.setHidden | ApiFunctions.setReadOnly | 
     ApiFunctions.setVisible | ApiFunctions.setDescription | ApiFunctions.setName */

On the other hand, if your TypeScript code really does care about the specific string literal types, then enum might not be right for your use case. Since TypeScript 3.4 introduced const assertions, it's been pretty easy to get an enum-like object. It looks like this:

export const ApiFunctions = {
  setHidden: "HIDE",
  setReadOnly: "SET_READ_ONLY",
  setVisible: "SHOW",
  setDescription: "SET_DESCRIPTION",
  setName: "SET_NAME",
  makeRequest: "MAKE_REQUEST"
} as const;

From this you can programmatically make a union type also called ApiFunctions:

export type ApiFunctions =
  typeof ApiFunctions[keyof typeof ApiFunctions];

These two definitions behave similarly to the enum version of ApiFunctions, except now the compiler lets you refer to the values both through the enum and as literal types:

export type ApiActions = Exclude<ApiFunctions, "MAKE_REQUEST">;
/* type ApiActions = "SET_NAME" | "HIDE" | "SET_READ_ONLY" | "SHOW" | "SET_DESCRIPTION" */

Which approach, if any, works better for you depends on your use case. I tend to avoid enums entirely wherever I can, because they're not part of JavaScript, and they have surprising behavior (especially numeric enums). But that's personal preference.

Playground link to code

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文