【问题标题】:How to lookup an Enum by value?如何按值查找枚举?
【发布时间】:2021-12-08 14:36:50
【问题描述】:

我有一个枚举,其中包含我已翻译并显示在前端的一堆 API 消息:

export enum API_MESSAGES {
  FAILED_TO_LOAD = 'Failed to load data',
  TOKEN_INVALID = 'Token seems to be invalid',
}

我想使用来自后端的错误来获取翻译后的值:

onError: (error: AxiosError<ApiError>) => {
  const errorRes = error.response?.data.error;
  console.log(API_MESSAGES[errorRes])
}

但是这会引发以下 TS 错误:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof API_MESSAGES'

No index signature with a parameter of type 'string' was found on type 'typeof API_MESSAGES'

我尝试过搜索,但没有找到访问枚举的简单方法。

【问题讨论】:

  • 使用类代替枚举并将其更改为对象。 export class API_MESSAGES { FAILED_TO_LOAD : '加载数据失败', TOKEN_INVALID :'令牌似乎无效', }
  • 如果error.response?.data.error 不是 API_MESSAGES 中的键之一会发生什么?
  • 我知道 error.response?.data.error 可能会失败,我将为它添加一个后备
  • 所以我推断您的后端不使用 Enum 定义,它发送 FAILED_TO_LOADTOKEN_INVALID 作为实际代码。然后枚举对此没有意义:(1)它们是程序级命名常量,而不是线级值和(2)类型安全没有意义/没有强制执行,因为后端不是使用相同的枚举定义。而是使用映射对象。请参阅我的更新答案。

标签: typescript enums


【解决方案1】:

字符串枚举不支持反向映射

API_MESSAGES[errorRes] 导致错误,因为根据 Typescript Handbook:

反向映射

除了为成员创建具有属性名称的对象外,数字枚举成员还获得从枚举值到枚举名称的反向映射。

数字枚举是

编译成一个同时存储正向(名称 -> 值)和反向(值 -> 名称)映射的对象。

请记住,字符串枚举成员根本不会生成反向映射。

但无论如何你都不想要反向映射!

您的目标是显示“翻译的”(对人类友好的)API 消息。因此,即使字符串枚举支持 API_MESSAGES[errorRes],您也会这样做:

'Token seems to be invalid' --> TOKEN_INVALID 

即与你想要的相反。

如果您的后端使用相同的枚举定义,它会发送“已翻译”的值!

假设您的后端代码使用相同的 API_MESSAGES 枚举,那么实际上它所做的只是使用“a set of named constants”,例如FAILED_TO_LOADTOKEN_INVALID,其值是翻译后的消息本身

换句话说,如果您的后端只是序列化API_MESSAGES 以响应前端,那么它已经在发送“已翻译”消息。

这意味着您的 onError 函数将是:

onError: (error: AxiosError<ApiError>) => {
  const errorRes = error.response?.data.error;
  console.log(errorRes)
}

从 Typescript 的角度和您的逻辑暗示,error.response?.data.error 的类型为 API_MESSAGES。因为 Typescript 枚举实际上只是命名常量,这意味着error.response?.data.error 实际上是以下联合类型

type API_MESSAGES = 'Failed to load data' | 'Token seems to be invalid',

同样,您将已经拥有翻译后的值。 FAILED_TO_LOADTOKEN_INVALID 将只是这些值的 Typescript names如果您的后端使用相同的枚举定义。

如果您的后端正在发送代码,您只需要一个映射对象

如果FAILED_TO_LOADTOKEN_INVALID 是后端发送的实际消息字符串,则只需要a mapping object instead of an enum

export const API_MESSAGES = {
  FAILED_TO_LOAD = 'Failed to load data',
  TOKEN_INVALID = 'Token seems to be invalid',
} as const;

// type API_MESSAGE = 'FAILED_TO_LOAD' | 'TOKEN_INVALID'
export type API_MESSAGE = keyof typeof API_MESSAGES

从后端传过来的错误代码是 只是一个普通的字符串,而不是一个类型化的枚举。这个convertServerMessage 函数演示了如何验证并将其转换为 API_MESSAGE 枚举。 As you said earlier,这是你的东西 做某处。您可能不需要这个单独的方法——相反,您可以将其逻辑放在读取服务器响应并构造 AxiosError 对象的代码部分中,其AxiosError.response.data.error 的类型应为API_MESSAGE

export function convertServerMessage(msg: string): API_MESSAGE {
    if (msg in API_MESSAGES) {
        return msg as API_MESSAGE
    } else {
        // error logic goes here
    }
}

那么您原来的onError 将按您的意愿工作(见底部注释):

onError: (error: AxiosError<ApiError>) => {
    // as I said above, the conversion from string code to 
    // enum should probably happen elsewere. I have it here
    // to keep the example simple.
    const errorRes = convertServerMessage(error.response?.data.error);
    console.log(API_MESSAGES[errorRes])
}

【讨论】:

  • 感谢这项工作!我仍然收到最初的 TS 警告之一:元素隐式具有“任何”类型,因为“字符串”类型的表达式不能用于索引类型但这可以通过添加 export const API_MESSAGES: { [key: string]:字符串 } = {
  • @Ronald... 警告是由于我在示例中的错误!我刚刚修好了。请参阅上面 API_MESSAGE 类型的正确类型声明,以及关于 convertServerMessage 的注释。这将比您的修复更类型安全,它基本上接受任何字符串。让我知道通过投票我的答案是否让一切变得完美(通常你应该投票并接受一个适合你的答案?)
  • 非常感谢!这确实有效,我现在已经投票并接受了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
  • 2010-12-17
  • 1970-01-01
  • 2012-06-16
  • 1970-01-01
  • 2015-02-12
相关资源
最近更新 更多