TypeScript:字符串枚举,轻松实现
枚举类型最常见的应用场景包括:
- 键及其关联的非字符串值
- 键,以及与这些键匹配的字符串值。
别误会我的意思。我只是不想重复 TypeScript 手册(https://www.typescriptlang.org/docs/handbook/enums.html)里写的所有内容。
第一个问题在 TypeScript 中可以很好地处理。只需使用:
enum MyEnum {
first,
second,
third
}
但第二个案例更像是这样:
enum MyStringEnum {
first = 'first',
second = 'second',
third = 'third'
}
随着值数量的增加,管理起来就变得越来越困难。而且我看到这里有很多重复的代码。此外,也容易出错。例如,可能会出现以下情况:
enum MyStringEnum {
first = 'fifth',
second = 'second',
third = 'third'
}
在手册中,可以看到从枚举类型进行反向查找所需的所有复杂性。
我的提议是构建一个简单的结构,您可以快速实施。
我们先来定义枚举中“键”的值:
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';
反向查找并非必要。但是,您需要一种方法来检查给定值在此枚举上下文中是否有效。为此,让我们编写一个类型断言器:
function isValid(param: unknown): asserts param is MyEnum {
assert( param && typeof param === 'string' && VALID_ENUM_VALUES.includes(param as MyEnum));
}
现在,在这个背景下:
const myStr = 'first';
if ( isValid(myStr)) {
// here, if 'myStr' is implicitly of type 'MyEnum'
console.log(`${myStr} is a valid Enum value`);
}
这种结构还有另一种用途,就是定义带键的对象。请看:
type MyRecordType = Record<MyEnum, unknown>;
// the 'myValue' below will error, because '{}' is not a valid value
const myValue: MyRecordType = {};
此处的类型定义等价于:
type MyRecordType = {
first: unknown;
second: unknown;
third: unknown;
}
您可以将“未知”更改为任何相关类型。这样,您就可以快速定义具有给定结构和已定义类型的对象。显然,更复杂的情况最好手动处理。
以下是同一问题的另一种变体:
type MyPartialRecordType = Partial<MyRecordType>;
// no error here
const myPartialValue: MyPartialRecordType = {};
这相当于:
type MyPartialRecordType = {
first?: unknown;
second?: unknown;
third?: unknown;
}
如果你想将这些方法结合起来使用,可以试试这个:
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>>;
这相当于:
type MixedRecord = {
one: unknown;
two: unknown;
} & {
three?: unknown;
four?: unknown;
}
或者,更简单地说:
type MixedRecord = {
one: unknown;
two: unknown;
three?: unknown;
four?: unknown;
}
现在,您可以创建联合类型、记录类型,还可以使用数组来验证值。
另一个有趣的例子,涉及映射类型:
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>;
}
这相当于:
type ParamType = {
id: string;
name: string;
contentsOfWallet: unknown;
}
这看起来似乎有点复杂,但其实可以用更少的篇幅来定义,不过看看它能做到什么程度:
- 有效的字段名称数组,可用于输入验证,例如在处理 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