使用枚举
Flow 枚举不是 联合类型 的语法。它们是自己的类型,并且枚举的每个成员都具有相同的类型。大型联合类型会导致性能问题,因为 Flow 必须将每个成员视为单独的类型。使用 Flow 枚举,无论您的枚举有多大,Flow 始终会表现出良好的性能,因为它只需要跟踪一种类型。
我们在下面的示例中使用以下枚举
enum Status {
Active,
Paused,
Off,
}
访问枚举成员
使用点语法访问成员
const status = Status.Active;
您不能使用计算访问
1enum Status {2 Active,3 Paused,4 Off,5}6const x = "Active";7Status[x]; // Error: computed access on enums is not allowed
7:8-7:8: Cannot access `x` on enum `Status` [1] because computed access is not allowed on enums. [invalid-enum-access]
用作类型注解
枚举声明同时定义了一个值(您可以从中访问枚举成员和方法)和一个同名的类型,该类型是枚举成员的类型。
function calculateStatus(): Status {
...
}
const status: Status = calculateStatus();
强制转换为表示类型
枚举不会隐式强制转换为其表示类型,反之亦然。如果您想从枚举类型转换为表示类型,可以使用显式强制转换 (x: string)
1enum Status {2 Active,3 Paused,4 Off,5}6
7const s: string = Status.Active; // Error: 'Status' is not compatible with 'string' 8const statusString: string = Status.Active as string;
7:19-7:31: Cannot assign `Status.Active` to `s` because `Status` [1] is incompatible with string [2]. You can explicitly cast your enum value to a string using `<expr> as string`. [incompatible-type]
要从可空枚举类型转换为可空字符串,您可以执行以下操作
const maybeStatus: ?Status = ....;
const maybeStatusString: ?string = maybeStatus && (maybeStatus as string);
如果您想从表示类型(例如 string
)转换为枚举类型(如果有效),请查看 cast 方法。
方法
枚举声明还定义了一些有用的方法。
下面,TEnum
是枚举的类型(例如 Status
),TRepresentationType
是该枚举的表示类型的类型(例如 string
)。
.cast
类型:cast(input: ?TRepresentationType): TEnum | void
cast
方法允许您安全地将原始值(如 string
)转换为枚举类型(如果它是枚举的有效值),否则为 undefined
。
const data: string = getData();
const maybeStatus: Status | void = Status.cast(data);
if (maybeStatus != null) {
const status: Status = maybeStatus;
// do stuff with status
}
使用 ??
运算符在一行中设置默认值
const status: Status = Status.cast(data) ?? Status.Off;
cast
的参数类型取决于枚举的类型。如果它是 字符串枚举,则参数类型将为 string
。如果它是 数字枚举,则参数类型将为 number
,依此类推。如果您希望强制转换 mixed
值,请先使用 typeof
细化
const data: mixed = ...;
if (typeof data === 'string') {
const maybeStatus: Status | void = Status.cast(data);
}
cast
使用 this
(表示枚举成员的对象),因此如果您想将函数本身作为值传递,则应使用箭头函数。例如
const strings: Array<string> = ...;
// WRONG: const statuses: Array<?Status> = strings.map(Status.cast);
const statuses: Array<?Status> = strings.map((input) => Status.cast(input)); // Correct
运行时成本:对于 镜像字符串枚举(例如 enum E {A, B}
),由于成员名称与值相同,因此运行时成本是恒定的 - 等效于调用 .hasOwnProperty
。对于其他枚举,在第一次调用时会创建一个 Map
,后续调用只需在缓存的映射上调用 .has
。因此成本是摊销的常数。
.isValid
类型:isValid(input: ?TRepresentationType): boolean
isValid
方法类似于 cast
,但只是返回一个布尔值:如果提供的输入是有效的枚举值,则返回 true
,否则返回 false
。
const data: string = getData();
const isStatus: boolean = Status.isValid(data);
isValid
使用 this
(表示枚举成员的对象),因此如果您想将函数本身作为值传递,则应使用箭头函数。例如
const strings: Array<string> = ...;
// WRONG: const statusStrings = strings.filter(Status.isValid);
const statusStrings = strings.filter((input) => Status.isValid(input)); // Correct
运行时成本:与上面 .cast
中描述的相同。
.members
类型:members(): Iterator<TEnum>
members
方法返回所有枚举成员的 迭代器(可迭代)。
const buttons = [];
function getButtonForStatus(status: Status) { ... }
for (const status of Status.members()) {
buttons.push(getButtonForStatus(status));
}
迭代顺序保证与声明中成员的顺序相同。
枚举本身不可枚举或不可迭代(例如,对枚举的 for-in/for-of 循环不会迭代其成员),您必须使用 .members()
方法来实现该目的。
您可以使用以下方法将可迭代对象转换为 Array
:Array.from(Status.members())
。您可以利用 Array.from
的第二个参数在构建数组的同时映射值:例如
const buttonArray = Array.from(
Status.members(),
status => getButtonForStatus(status),
);
.getName
类型:getName(value: TEnum): string
getName
方法将枚举值映射到该值枚举成员的字符串名称。使用 number
/boolean
/symbol
枚举时,这对于调试和生成内部 CRUD UI 很有用。例如
enum Status {
Active = 1,
Paused = 2,
Off = 3,
}
const status: Status = ...;
console.log(Status.getName(status));
// Will print a string, either "Active", "Paused", or "Off" depending on the value.
运行时成本:与上面 .cast
中描述的相同。一个缓存的从枚举值到枚举名称的反向映射用于 .cast
、.isValid
和 .getName
。这些方法的第一次调用将创建此缓存映射。
使用 switch
穷举检查枚举
在 switch
语句中检查枚举值时,我们强制您检查所有可能的枚举成员,并且不包含冗余情况。这有助于确保您在编写使用枚举的代码时考虑所有可能性。当添加或删除成员时,它特别有助于重构,因为它会指出您需要更新的不同位置。
const status: Status = ...;
switch (status) { // Good, all members checked
case Status.Active:
break;
case Status.Paused:
break;
case Status.Off:
break;
}
您可以使用 default
来匹配到目前为止未检查的所有成员
switch (status) {
case Status.Active:
break;
default: // When `Status.Paused` or `Status.Off`
break;
}
您可以在一个 switch case 中检查多个枚举成员
switch (status) {
case Status.Active:
case Status.Paused:
break;
case Status.Off:
break;
}
您必须匹配枚举的所有成员(或提供 default
case)
1enum Status {2 Active = 1,3 Paused = 2,4 Off = 3,5}6const status: Status = Status.Active;7
8// Error: you haven't checked 'Status.Off' in the switch9switch (status) { 10 case Status.Active:11 break;12 case Status.Paused:13 break;14}
9:9-9:14: Incomplete exhaustive check: the member `Off` of enum `Status` [1] has not been considered in check of `status`. [invalid-exhaustive-check]
您不能重复 case(因为这将是死代码!)
1enum Status {2 Active = 1,3 Paused = 2,4 Off = 3,5}6const status: Status = Status.Active;7
8switch (status) {9 case Status.Active:10 break;11 case Status.Paused:12 break;13 case Status.Off:14 break;15 case Status.Paused: // Error: you already checked for 'Status.Paused' 16 break;17}
15:8-15:20: Invalid exhaustive check: case checks for enum member `Paused` of `Status` [1], but member `Paused` was already checked at case [2]. [invalid-exhaustive-check]
如果您已经匹配了所有情况,则 default
case 是冗余的
1enum Status {2 Active = 1,3 Paused = 2,4 Off = 3,5}6const status: Status = Status.Active;7
8switch (status) {9 case Status.Active:10 break;11 case Status.Paused:12 break;13 case Status.Off:14 break;15 default: // Error: you've already checked all cases, the 'default' is redundant 16 break; 17}18// The following is OK because the `default` covers the `Status.Off` case:19switch (status) {20 case Status.Active:21 break;22 case Status.Paused:23 break;24 default:25 break;26}
15:3-16:10: Invalid exhaustive check: default case checks for additional enum members of `Status` [1], but all of its members have already been checked. [invalid-exhaustive-check]
除非您正在切换具有 未知成员 的枚举。
如果您将穷举检查的 switch 嵌套在穷举检查的 switch 中,并且从每个分支返回,则必须在嵌套的 switch 后添加 break;
switch (status) {
case Status.Active:
return 1;
case Status.Paused:
return 2;
case Status.Off:
switch (otherStatus) {
case Status.Active:
return 1;
case Status.Paused:
return 2;
case Status.Off:
return 3;
}
break;
}
请记住,您可以在 switch case 中添加块。如果您想使用局部变量,它们很有用
switch (status) {
case Status.Active: {
const x = f();
...
break;
}
case Status.Paused: {
const x = g();
...
break;
}
case Status.Off: {
const y = ...;
...
break;
}
}
如果您在此示例中没有添加块,则 const x
的两个声明将发生冲突并导致错误。
枚举在 if
语句或 switch
语句以外的其他上下文中不会被穷举检查。
使用未知成员进行穷举检查
如果您的枚举具有 未知成员(使用 ...
指定),例如
enum Status {
Active,
Paused,
Off,
...
}
那么在切换枚举时始终需要 default
。default
检查您未显式列出的“未知”成员。
switch (status) {
case Status.Active:
break;
case Status.Paused:
break;
case Status.Off:
break;
default:
// Checks for members not explicitly listed
}
您可以使用 require-explicit-enum-switch-cases
Flow Lint 来要求所有已知成员都作为 case 显式列出。例如
1enum Status {2 Active = 1,3 Paused = 2,4 Off = 3,5}6const status: Status = Status.Active;7
8// flowlint-next-line require-explicit-enum-switch-cases:error9switch (status) { 10 case Status.Active:11 break;12 case Status.Paused:13 break;14 default:15 break;16}
9:9-9:14: Incomplete exhaustive check: the member `Off` of enum `Status` [1] has not been considered in check of `status`. The default case [2] does not check for the missing members as the `require-explicit-enum-switch-cases` lint has been enabled. [require-explicit-enum-switch-cases]
您可以通过执行以下操作来修复它
// flowlint-next-line require-explicit-enum-switch-cases:error
switch (status) {
case Status.Active:
break;
case Status.Paused:
break;
case Status.Off: // Added the missing `Status.Off` case
break;
default:
break;
}
require-explicit-enum-switch-cases
代码风格检查不是全局启用的,而是在您需要该行为时在每个 switch
基础上启用。对于普通枚举,对于其上的每个 switch
语句,您可以提供 default
或不提供,从而决定是否需要显式列出每个 case。类似地,对于具有未知成员的 Flow 枚举,您也可以在每个 switch 基础上启用此代码风格检查。
代码风格检查适用于常规 Flow 枚举类型的 switch。它实际上禁止在该 switch
语句中使用 default
,方法是要求将所有枚举成员作为 case 显式列出。
将枚举映射到其他值
您可能出于各种原因希望将枚举值映射到另一个值,例如标签、图标、元素等。
在以前的模式中,通常使用对象字面量来实现此目的,但是使用 Flow 枚举,我们更喜欢包含 switch 的函数,我们可以对这些函数进行穷举检查。
而不是
const STATUS_ICON: {[Status]: string} = {
[Status.Active]: 'green-checkmark',
[Status.Paused]: 'grey-pause',
[Status.Off]: 'red-x',
};
const icon = STATUS_ICON[status];
它实际上并不能保证我们将每个 Status
映射到某个值,请使用
function getStatusIcon(status: Status): string {
switch (status) {
case Status.Active:
return 'green-checkmark';
case Status.Paused:
return 'grey-pause';
case Status.Off:
return 'red-x';
}
}
const icon = getStatusIcon(status);
将来,如果您添加或删除枚举成员,Flow 会告诉您也更新 switch,使其始终准确。
如果您实际上想要一个非穷举的字典,可以使用 Map
const counts = new Map<Status, number>([
[Status.Active, 2],
[Status.Off, 5],
]);
const activeCount: Status | void = counts.get(Status.Active);
Flow 枚举不能用作对象字面量中的键,如 本页后面所述。
联合中的枚举
如果您的枚举值在联合中(例如 ?Status
),请先细化到仅枚举类型
const status: ?Status = ...;
if (status != null) {
status as Status; // 'status' is refined to 'Status' at this point
switch (status) {
case Status.Active: break;
case Status.Paused: break;
case Status.Off: break;
}
}
如果您想细化到枚举值,可以使用 typeof
与表示类型一起使用,例如
const val: Status | number = ...;
// 'Status' is a string enum
if (typeof val === 'string') {
val as Status; // 'val' is refined to 'Status' at this point
switch (val) {
case Status.Active: break;
case Status.Paused: break;
case Status.Off: break;
}
}
导出枚举
枚举是一种类型和一个值(就像类一样)。要导出类型和值,请像导出值一样导出它
1export enum Status {}
或者,作为默认导出(注意:您必须始终指定枚举名称,export default enum {}
不允许)
1export default enum Status {}
使用 CommonJS
1enum Status {}2module.exports = Status;
要仅导出其类型,而不是值,您可以执行以下操作
1enum Status {}2export type {Status};
文件中的其他函数仍然可以访问枚举实现。
导入枚举
如果您已像这样导出枚举
1// status.js2export default enum Status {3 Active,4 Paused,5 Off,6}
您可以像这样将其导入为值和类型
import Status from 'status';
const x: Status /* used as type */ = Status.Active /* used as value */;
如果您只需要使用类型,可以将其导入为类型
import type Status from 'status';
function printStatus(status: Status) {
...
}
使用 CommonJS
const Status = require('status');
泛型枚举
目前尚无方法指定泛型枚举类型,但已经收到了很多请求,因此我们将在将来对此进行研究。
对于通用枚举的一些用例,您目前可以要求用户提供调用枚举方法的函数(而不是传递枚举本身),例如
function castToEnumArray<TRepresentationType, TEnum>(
f: TRepresentationType => TEnum,
xs: Array<TRepresentationType>,
): Array<TEnum | void> {
return xs.map(f);
}
castToEnumArray((input) => Status.cast(input), ["Active", "Paused", "Invalid"]);
何时不使用枚举
枚举旨在涵盖许多用例并展现某些优势。该设计做出了一系列权衡以实现这一点,在某些情况下,这些权衡可能不适合您。在这些情况下,您可以继续使用现有模式来满足您的用例。
不同的对象键
您不能使用枚举成员作为不同的对象键。
以下模式有效,因为LegacyStatus.Active
和LegacyStatus.Off
的类型不同。一个类型为'Active'
,另一个类型为'Off'
。
1const LegacyStatus = Object.freeze({2 Active: 'Active',3 Paused: 'Paused',4 Off: 'Off',5});6const o = {7 [LegacyStatus.Active]: "hi",8 [LegacyStatus.Off]: 1,9};10const x: string = o[LegacyStatus.Active]; // OK11const y: number = o[LegacyStatus.Off]; // OK12const z: boolean = o[LegacyStatus.Active]; // Error - as expected
12:20-12:41: Cannot assign `o[LegacyStatus.Active]` to `z` because string [1] is incompatible with boolean [2]. [incompatible-type]
我们不能对枚举使用相同的模式。所有枚举成员都具有相同的类型,即枚举类型,因此 Flow 无法跟踪键和值之间的关系。
如果您希望从枚举值映射到另一个值,您应该使用具有完全检查的 switch 的函数。
不相交的对象联合
枚举的一个定义特征是,与联合不同,每个枚举成员不形成它自己的独立类型。每个成员都具有相同的类型,即枚举类型。这允许 Flow 以一致快速的方式分析枚举用法,但这也意味着在某些需要独立类型的场景中,我们无法使用枚举。考虑以下联合,遵循不相交的对象联合模式
1type Action =2 | {type: 'Upload', data: string}3 | {type: 'Delete', id: number};
联合中的每个对象类型都具有一个共同字段(type
),用于区分我们正在处理的哪种对象类型。
我们不能对该字段使用枚举类型,因为为了使该机制起作用,该字段的类型必须在联合的每个成员中都不同,但枚举成员都具有相同的类型。
将来,我们可能会添加枚举的功能,使其除了键和原始值之外还可以封装其他数据 - 这将使我们能够替换不相交的对象联合。
保证内联
Flow 枚举旨在允许内联(例如,成员值必须是字面量,枚举是冻结的),但是内联本身需要是构建系统(无论您使用什么)的一部分,而不是 Flow 本身。
虽然枚举成员访问(例如Status.Active
)可以内联(除了符号枚举,由于符号的性质,它们不能内联),但其方法的用法(例如Status.cast(x)
)不能内联。