在 TypeScript 中使用不区分大小写的键索引枚举对象

bom*_*azo 4 javascript enums typescript

虽然不是必需的,但枚举键通常全部以大写字母声明。但是,枚举值可以与键本身不同。

declare enum MyEnum {
    PRODUCTION = "production",
    DEVELOPMENT = "development",
    SANDBOX = "sandbox"
}
Run Code Online (Sandbox Code Playgroud)

如果我要使用与我的枚举值匹配的外部字符串值来索引我的枚举,它将不起作用,因为键与值不同:

const lowercase = "production";
const UPPERCASE = "PRODUCTION";

const bad = MyEnum[lowercase]; // does not work
const good = MyEnum[UPPERCASE]; // works
Run Code Online (Sandbox Code Playgroud)

我想使用枚举值作为索引枚举的方式。我是否被迫始终匹配相同的枚举值和枚举键才能动态索引枚举,或者我是否必须使用一些字符串值操作来确保键与值匹配?

jca*_*alz 5

TypeScript 中的枚举作为普通 JavaScript 对象发出。因此,在某种程度上,这个问题实际上是关于如何使用不区分大小写的键访问 JavaScript 对象。简而言之,你不能,你需要解决它。更长的答案是,您可能可以为您的对象构建一个Proxy按照您想要的方式运行的对象,但这比仅仅解决它要复杂得多,尤其是任何能让 TypeScript 相信这就是您正在做的事情。


如果您知道对象键都是大写的,那么最简单的解决方法是在索引到对象之前自行将候选键大写。TypeScript 不理解s的方法toUpperCase()string字符串文字类型的作用。例如,如果您有一个vtype值"foo",编译器将无法理解它v.toUpperCase()是一个 type 值"FOO"

let x: "FOO" = "foo".toUpperCase(); // error!
Run Code Online (Sandbox Code Playgroud)

但您可以为该toUpperCase()方法编写一个包装器,其行为如下:

const uc = <T extends string>(x: T) => x.toUpperCase() as Uppercase<T>;

let x: "FOO" = uc("foo"); // okay
Run Code Online (Sandbox Code Playgroud)

这里在其返回类型中uc使用内部类型Uppercase<T>

uc()因此,这里的解决方法是在索引之前传递键:

const UPPERCASE = "PRODUCTION";
const good = MyEnum[uc(UPPERCASE)]; // okay
// const good: MyEnum.PRODUCTION

const lowercase = "production";
const stillGood = MyEnum[uc(lowercase)]; // okay
// const stillGood: MyEnum.PRODUCTION
Run Code Online (Sandbox Code Playgroud)

如果您知道键都是大写的,那么您需要在对象中搜索合适的键。您可以编写一个辅助函数来执行此操作,甚至给它一个 TypeScript 可以使用的调用签名:

function idxIgnoreCase<K extends string, T extends object>(
    obj: T,
    k: K extends (
        Uppercase<K> extends Uppercase<Extract<keyof T, string>> ? unknown : never
    ) ? K : Extract<keyof T, string>
): { [P in Extract<keyof T, string>]-?:
    Uppercase<P> extends Uppercase<K> ? T[P] : never
}[Extract<keyof T, string>] {
    return Object.entries(obj).find(
        ([p, v]) => k.toUpperCase() === p.toUpperCase())?.[1] as any;
}
Run Code Online (Sandbox Code Playgroud)

这很丑陋,但它有效:

const ret = idxIgnoreCase(MyEnum, "dEvElOpMeNt");
// const ret: MyEnum.DEVELOPMENT
console.log(ret) // "development"
Run Code Online (Sandbox Code Playgroud)

我什至不打算尝试将辅助函数编写为 a Proxy,因为这样做需要告诉 TypeScript 结果对象有数百个键,每个键对应原始键的每种可能情况。而这一切都是为了用写作MyProxyEnum.dEvElOpMeNt代替f(MyEnum, "dEvElOpMeNt"). 特别是因为其他查看代码的 TypeScript 和 JavaScript 开发人员可能会非常困惑,因为他们期望区分大小写的键。

同样,在索引之前修改键的解决方法是迄今为止最简单且最容易理解的方法。

Playground 代码链接