发布于 2026-01-06 0 阅读
0

TypeScript:字符串枚举,轻松实现

TypeScript:字符串枚举,轻松实现

枚举类型最常见的应用场景包括:

  • 键及其关联的非字符串值
  • 键,以及与这些键匹配的字符串值。

别误会我的意思。我只是不想重复 TypeScript 手册(https://www.typescriptlang.org/docs/handbook/enums.html)里写的所有内容。

第一个问题在 TypeScript 中可以很好地处理。只需使用:

enum MyEnum {
  first,
  second,
  third
}
Enter fullscreen mode Exit fullscreen mode

但第二个案例更像是这样:

enum MyStringEnum {
  first = 'first',
  second = 'second',
  third = 'third'
}
Enter fullscreen mode Exit fullscreen mode

随着值数量的增加,管理起来就变得越来越困难。而且我看到这里有很多重复的代码。此外,也容易出错。例如,可能会出现以下情况:

enum MyStringEnum {
  first = 'fifth',
  second = 'second',
  third = 'third'
}
Enter fullscreen mode Exit fullscreen mode

在手册中,可以看到从枚举类型进行反向查找所需的所有复杂性。

我的提议是构建一个简单的结构,您可以快速实施。

我们先来定义枚举中“键”的值:

const VALID_ENUM_VALUES = ['first', 'second', 'third'] as const;

注意as const语句末尾的那个引号。这才是关键所在。

让我们定义一下代码中可以使用的类型,以确保我们不会使用任何无效值:
type MyEnum = typeof VALID_ENUM_VALUES[number];

如果你在 VSCode 中输入这段代码,并将鼠标悬停在上面MyEnum,你应该会看到这相当于定义了:
type MyEnum = 'first' | 'second' | 'third';

[number]告诉 TypeScript 获取数组的所有“基于数字的下标”。

另一个优点是,如果你对VALID_ENUM_VALUES数组进行更改,MyEnum更改也会随之更改。

所以,如果你在编辑器中输入以下代码:

console.log("Valid values of the enum are:", VALID_ENUM_VALUES);
const valueToCheck = 'first';
console.log(`Check if '${valueToCheck}' is part of the enum`, VALID_ENUM_VALUES.includes(valueToCheck))

// Error here, because "hello" is not a value in the VALID_ENUM_VALUES array.
const typedVar: MyEnum = 'hello';
Enter fullscreen mode Exit fullscreen mode

反向查找并非必要。但是,您需要一种方法来检查给定值在此枚举上下文中是否有效。为此,让我们编写一个类型断言器:

function isValid(param: unknown): asserts param is MyEnum {
    assert( param && typeof param === 'string' && VALID_ENUM_VALUES.includes(param as MyEnum));
}
Enter fullscreen mode Exit fullscreen mode

现在,在这个背景下:

const myStr = 'first';
if ( isValid(myStr)) {
  // here, if 'myStr' is implicitly of type 'MyEnum'
  console.log(`${myStr} is a valid Enum value`);
}
Enter fullscreen mode Exit fullscreen mode

这种结构还有另一种用途,就是定义带键的对象。请看:

type MyRecordType = Record<MyEnum, unknown>;

// the 'myValue' below will error, because '{}' is not a valid value
const myValue: MyRecordType = {};

Enter fullscreen mode Exit fullscreen mode

此处的类型定义等价于:

type MyRecordType = {
  first: unknown;
  second: unknown;
  third: unknown;
}
Enter fullscreen mode Exit fullscreen mode

您可以将“未知”更改为任何相关类型。这样,您就可以快速定义具有给定结构和已定义类型的对象。显然,更复杂的情况最好手动处理。

以下是同一问题的另一种变体:

type MyPartialRecordType = Partial<MyRecordType>;
// no error here
const myPartialValue: MyPartialRecordType = {};
Enter fullscreen mode Exit fullscreen mode

这相当于:

type MyPartialRecordType = {
  first?: unknown;
  second?: unknown;
  third?: unknown;
}
Enter fullscreen mode Exit fullscreen mode

如果你想将这些方法结合起来使用,可以试试这个:

const MUST_HAVE_PARAMS = ['one', 'two'] as const;
type MandatoryParams = typeof MUST_HAVE_PARAMS[number];
const OPTIONAL_PARAMS = ['three', 'four'] as const;
type OptionalParams = typeof OPTIONAL_PARAMS[number];
type MixedRecord = Record<MandatoryParams, unknown> & Partial<Record<OptionalParams, unknown>>;
Enter fullscreen mode Exit fullscreen mode

这相当于:

type MixedRecord = {
    one: unknown;
    two: unknown; 
} & {
    three?: unknown;
    four?: unknown;
}
Enter fullscreen mode Exit fullscreen mode

或者,更简单地说:

type MixedRecord = {
    one: unknown;
    two: unknown; 
    three?: unknown;
    four?: unknown;
}
Enter fullscreen mode Exit fullscreen mode

现在,您可以创建联合类型、记录类型,还可以使用数组来验证值。

另一个有趣的例子,涉及映射类型:

const KNOWN_PARAMS_TYPES = ['id', 'name'] as const;
type KnownParams = typeof KNOWN_PARAMS_TYPES[number];

const UNKNOWN_PARAMS_TYPES = ['contentsOfWallet'] as const;
type UnknownParams = typeof UNKNOWN_PARAMS_TYPES[number];

type AllParams = KnownParams | UnknownParams;

type ValueType<T extends AllParams> = T extends KnownParams ? string : unknown;
type ParamType = {
    [Property in AllParams]: ValueType<Property>;
}
Enter fullscreen mode Exit fullscreen mode

这相当于:

type ParamType = {
    id: string;
    name: string;
    contentsOfWallet: unknown;
}
Enter fullscreen mode Exit fullscreen mode

这看起来似乎有点复杂,但其实可以用更少的篇幅来定义,不过看看它能做到什么程度:

  • 有效的字段名称数组,可用于输入验证,例如在处理 HTTP 查询字符串并想要检查参数名称是否有效时。
  • 用于应用程序代码中的字符串联合类型,适用于那些原本需要使用key of ParamType字符串类型的地方。
  • 它会随着你向已知/未知部分添加更多参数而自我更新。

总而言之,如果您需要在应用程序的各个地方使用一个值数组,并且仍然需要类型安全的数据结构,那么这种组织方式将极大地帮助您利用 TypeScript 的强大功能来扩展代码。

本文最初由 Navneet Karnani ( navneet@mandraketech.in ) 发布在他的博客上,网址为:https://blog.mandraketech.in/typescript-string-enums

文章来源:https://dev.to/mandraketech/typescript-string-enums-the-easy-way-1ke4