Flow 数字枚举(Number Enum)实战:用 `of number` 与穷尽 switch 编写 HTTP 状态码分类器
2026/9/20 17:03:50 网站建设 项目流程

Flow 数字枚举(Number Enum)实战:用of number与穷尽 switch 编写 HTTP 状态码分类器

【免费下载链接】flowAdds static typing to JavaScript to improve developer productivity and code quality.项目地址: https://gitcode.com/gh_mirrors/flow30/flow

本指南以 Flow 官方评测用例 enum_002_number_enum_switch 为核心场景,完整讲解 Flow Enum 中数字枚举(Number Enum)的定义约束、表示类型(representation type)的显式转换,以及基于switch的穷尽检查(exhaustive check)在真实业务逻辑中的落地方式。读完本文,你将能够独立用 Flow Enum 建模带数值语义的领域类型(如 HTTP 状态码、错误码、渠道 ID),写出被编译器强制保证覆盖所有分支、且无冗余分支的类型安全代码。

任务背景:一个 HTTP 状态分类器

评测任务要求编写一个 HTTP 状态分类器,核心产出是一个数字枚举和三个基于穷尽switch的函数:

  • 定义枚举HttpStatus,成员及数值为:Ok(200)、Created(201)、BadRequest(400)、Unauthorized(401)、NotFound(404)、InternalError(500);
  • isSuccess(status: HttpStatus): boolean—— 2xx 返回true,其余返回false
  • toStatusLine(status: HttpStatus): string—— 输出形如"200 OK""404 Not Found"的字符串,且不能在消息中硬编码枚举数值,必须把枚举值转换回底层数字再拼接;
  • retryable(status: HttpStatus): boolean—— 仅InternalError返回true(服务器可恢复),其余返回false

从评测目录结构(evals/evals/02_unique_features/enum_002_number_enum_switch/)可以看到,该评测包含任务描述prompt.md、输入占位input/main.js、期望实现ideal/main.jsconfig.json评分配置,属于 Flow 官方评测集中 "unique_features"(独有特性)类别下难度为 medium 的用例。

第一步:用of number定义数字枚举

Flow 官方文档 Defining enums 指出,枚举成员只能是 string、number、boolean、bigint、symbol 五种类型之一,且成员类型必须一致。其中数字枚举必须显式给出成员值,这是与字符串枚举最大的区别:

// @flow export enum HttpStatus of number { Ok = 200, Created = 201, BadRequest = 400, Unauthorized = 401, NotFound = 404, InternalError = 500, }

需要注意的约束(见 defining-enums.md#toc-number-enums):

  • 值必须是字面量Ok = 200 + 1这类计算表达式会被拒绝("the value must be a literal")。Flow 额外允许负数作为初始值(JS 中负数不是字面量,但enum E { A = -1 }合法)。
  • 不允许自动编号:数字枚举没有默认值机制。官方文档解释其原因:如果允许enum {A, B, C}自动编号,从中删除中间成员会连锁改变后续成员的值,对持久化、日志、推送等场景是危险的,因此要求开发者显式写明每个数值。
  • 成员值必须唯一Created = 201之后不能再出现另一个值为 201 的成员。
  • of number子句的作用:它不影响类型检查行为,只保证在定义处所有成员都是number。当值写错类型时,错误信息会始终按数字枚举来解释。
  • 成员命名规范:成员名须为合法标识符且不能以小写az开头(小写开头保留给枚举方法,如Status.cast(...));官方风格建议使用PascalCase,枚举名用单数且不要加Enum后缀。

表示类型与显式转换:为什么必须status as number

Flow Enum 是一个全新的名义类型(nominal type),每个成员共享同一个枚举类型,而不是各自成为字面量类型。官方文档 Using enums 明确:枚举不会隐式转换为表示类型,表示类型也不会隐式转换为枚举

因此toStatusLine中想要拿到数值 200、404,必须显式转换:

const code: number = status as number;

as强制转换是官方推荐的方式;对于泛型场景(不确定具体表示类型)可以使用.valueOf()方法:

declare const status: HttpStatus; const code: number = status.valueOf(); // 等价于 status as number

反过来,把number转回枚举则使用.cast(input)方法(合法值返回对应成员,否则返回undefined),或.isValid(input)判断合法性。这也是该评测的兄弟用例 enum_001_string_enum_cast 所考察的能力。注意:toStatusLine要求消息文本中不重复硬编码数值,正是为了强制你走"枚举 → 表示类型"的转换路径,而不是直接写'200 OK'绕过枚举。

穷尽 switch:编译器帮你保证分支全覆盖

三个函数的核心都是对枚举做穷尽switch。在 Flow 中,对枚举值使用switch时,编译器强制要求覆盖所有枚举成员,漏掉任一成员会报[invalid-exhaustive-check]错误并指名遗漏的具体成员;重复 case 会报死代码错误;已覆盖全部分支后再写default会被判为冗余(详见 using-enums.md#toc-exhaustively-checking-enums-with-a-switch)。

期望实现 ideal/main.js 展示了标准写法:

export function isSuccess(status: HttpStatus): boolean { switch (status) { case HttpStatus.Ok: case HttpStatus.Created: return true; case HttpStatus.BadRequest: case HttpStatus.Unauthorized: case HttpStatus.NotFound: case HttpStatus.InternalError: return false; } } export function retryable(status: HttpStatus): boolean { switch (status) { case HttpStatus.InternalError: return true; case HttpStatus.Ok: case HttpStatus.Created: case HttpStatus.BadRequest: case HttpStatus.Unauthorized: case HttpStatus.NotFound: return false; } }

这段代码体现了三个要点:

  1. 多个成员共用一个 casecase HttpStatus.Ok: case HttpStatus.Created:连续堆叠,Flow 允许在一个分支中匹配多个成员。
  2. 无需default:6 个成员全部被 case 覆盖,函数天然满足"所有路径都有返回值",Flow 不会报"缺少返回语句"。
  3. 重构友好:未来若给HttpStatus新增成员(如Teapot = 418),Flow 会在这三个switch处逐一报[invalid-exhaustive-check],精确告诉你哪些函数需要更新——这正是官方文档强调的枚举在重构时的价值。

对于不允许default的严格场景,可借助 Flow Lint 规则require-explicit-enum-switch-cases(按switch粒度启用),它通过禁止该switch中的default来强制显式列出全部成员。

完整解法:toStatusLine 的消息拼接

toStatusLine是把前面所有知识点串起来的地方——先转换表示类型,再穷尽switch

export function toStatusLine(status: HttpStatus): string { const code: number = status as number; switch (status) { case HttpStatus.Ok: return `${code} OK`; case HttpStatus.Created: return `${code} Created`; case HttpStatus.BadRequest: return `${code} Bad Request`; case HttpStatus.Unauthorized: return `${code} Unauthorized`; case HttpStatus.NotFound: return `${code} Not Found`; case HttpStatus.InternalError: return `${code} Internal Server Error`; } }

status as number在进入switch之前就把枚举成员转换为其底层数值,之后每个分支只需关心消息文本,数值统一由code提供,避免"枚举数值散落在字符串里"的重复维护问题。将状态码与文案一一对应的模式,也正是官方文档推荐的"用带穷尽 switch 的函数做枚举映射"(见 using-enums.md#toc-mapping-enums-to-other-values)——相比{[key: Status]: string}字面量字典,switch 版本能保证每个成员都被映射到。

底层实现与评测依据

评测如何判定(config.json 的 AST 评分)

评测配置 config.json 采用 AST 节点匹配评分,要求生成的代码必须包含:

{ "grading": { "graders": [ { "type": "contains_ast_node_type", "query": "EnumDeclaration" }, { "type": "contains_ast_node_type", "query": "EnumNumberMember" } ] } }

即解法中必须出现EnumDeclaration(枚举声明)与EnumNumberMember(数字枚举成员)两类 AST 节点。这从实现层面印证了:使用of number的数字枚举在 Flow 语法树中是独立节点类型,与字符串枚举(EnumStringMember)、布尔枚举等区分。

运行时行为(Babel 变换 + flow-enums-runtime)

从官方文档 defining-enums.md#toc-enums-at-runtime 可知,枚举声明在运行时被babel-plugin-transform-flow-enums变换为对flow-enums-runtime(本仓库对应 packages/flow-enums-runtime)的调用:

  • 枚举对象以Object.create(null)为原型(原型上挂枚举方法),避免Object.prototype属性污染;
  • 唯一的自有属性就是各枚举成员,且成员不可枚举;
  • 整个枚举对象被Object.freeze冻结,运行期无法增删改成员——这与"枚举固定于声明处"的设计一致。

对于.cast.isValid.getName这三个方法,首次调用时会构建并缓存一张反向映射表(值 → 成员名),后续调用分摊为常数时间;镜像字符串枚举(成员名即值)的cast开销等价于一次hasOwnProperty

仓库中的佐证

  • 枚举语法与合法/非法形态的完整测试集:tests/enums/,例如 valid.js 展示了基础声明与"声明前使用类型"的用法,error-duplicate-values.jserror-modification.jsvalue-of.js等分别覆盖重复值、修改枚举、valueOf等行为;
  • 官方完整参考文档:defining-enums.md 与 using-enums.md;
  • 同系列的相邻评测:字符串枚举转换 enum_001_string_enum_cast、布尔/symbol 枚举 enum_003_boolean_symbol、match表达式的枚举穷尽检查 enum_008_enum_match、数字大整数枚举 enum_009_bigint_enum。

延伸:这套模式还能怎么用

数字枚举 + 穷尽 switch 的组合适合所有"携带数值语义的封闭集合"建模:

  • 状态码/错误码:本任务的 HTTP 状态码;类似的还有业务错误码、平台错误号;
  • 渠道/类型 IDenum Channel of number { Web = 1, IOS = 2, Android = 3 },配合Channel.cast(input)安全地把外部传入的数字转成枚举,非法值落回undefined再走默认分支;
  • 协议常量enum OpCode of number { Heartbeat = 0, Data = 1, Close = 2 },网络解析场景中用status as number序列化、用.cast反序列化。

同时要注意 Flow Enum 的边界(using-enums.md#toc-when-to-not-use-enums):所有成员共享同一枚举类型,因此不能把枚举成员当"各有独立字面量类型"的 key 来构建每个 key 映射不同值类型的对象;需要这种能力时,应回到带穷尽 switch 的函数映射模式。若枚举值需要跨越进程/版本边界(如客户端与服务端各持一份声明),可在声明末尾加...声明"未知成员",此后switch必须带defaultmatch必须带_通配符。

小结

从 prompt.md 这个评测任务出发,我们完整走通了 Flow 数字枚举的实战链路:用of number+ 字面量定义数值枚举、用as number/.valueOf()显式转换表示类型、用穷尽switch获得编译器保证的分支覆盖,并通过 ideal/main.js 与 config.json 验证了解法的结构与评分依据。将这套"枚举建模 + 穷尽分支 + 显式转换"的组合应用到你的领域类型建模中,可以让一类极易出错的分支逻辑在编译期就被完全约束。

【免费下载链接】flowAdds static typing to JavaScript to improve developer productivity and code quality.项目地址: https://gitcode.com/gh_mirrors/flow30/flow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询