Vue 3 + TypeScript 中 enum 的使用实践

在 Vue 3 + TypeScript 项目中,enum 的选择不仅涉及类型定义,还涉及 Vue SFC 的编译规则、模块导出方式,以及 Vite 单文件转译带来的限制。

本文从跨模块导入与导出、编译语义、方案选型三个方面,总结实际开发中的使用经验。

一、跨模块导入与导出

1.1 .vue 文件与 .ts 文件的区别

在 Vue 组件中,私有枚举可以直接声明在 <script setup lang="ts"> 中:

html 复制代码
<script setup lang="ts">
enum Status {
  Pending = 0,
  Success = 1,
}

const status = Status.Pending
</script>

<template>
  <div v-if="status === Status.Pending">等待中</div>
  <div v-else>已完成</div>
</template>

<script setup> 中的顶层绑定可以直接在模板中使用,普通 enum 和 const enum 都能被 Vue 编译器识别为模板绑定。需要注意,编译器能识别绑定,不等于所有构建链都能正确处理 const enum。

1.2 跨组件共享:优先在 .ts 中声明

当枚举需要被多个组件、工具函数或业务模块使用时,建议将它独立为一个 TypeScript 模块。

ts 复制代码
// src/enums/order.ts
export enum OrderStatus {
  Pending = 'pending',
  Processing = 'processing',
  Completed = 'completed',
}

Vue 组件中通过普通值导入使用:

html 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { OrderStatus } from '@/enums/order'

const status = ref(OrderStatus.Pending)
</script>

<template>
  <div v-if="status === OrderStatus.Completed">
    订单已完成
  </div>
</template>

这里需要使用普通的 import,因为模板要访问 OrderStatus.Completed 这个运行时值。

如果只需要枚举类型,而不需要访问枚举成员,则可以在纯 .ts 文件中使用 import type:

ts 复制代码
import type { OrderStatus } from '@/enums/order'

function handleStatus(status: OrderStatus) {
  // 仅在类型层面使用 status
}

1.3 为什么不建议直接从 <script setup> 导出枚举?

<script setup> 不是普通的 ES 模块脚本区域,不支持直接通过顶层 export 导出声明:

ts 复制代码
<script setup lang="ts">
// 不要这样写
export enum Status {
  Pending = 0,
  Success = 1,
}
</script>

如果确实需要命名导出,可以使用普通 <script>,但这会让组件同时承担组件定义和公共模块导出的职责,增加维护成本。

更清晰的结构是:

css 复制代码
src/
├── enums/
│   └── order.ts
├── components/
│   └── OrderStatus.vue
└── index.ts

通过独立模块维护枚举,组件只负责展示和交互。如果项目需要统一导出,也可以在入口文件中转发:

ts 复制代码
export { OrderStatus } from './enums/order'
export { default as OrderStatusBadge } from './components/OrderStatus.vue'

实践原则:组件内部使用的枚举可以就近声明;跨模块共享的枚举应优先放在 .ts 文件中。

二、enum 的编译语义

2.1 普通 enum 会生成运行时对象

例如:

ts 复制代码
enum Direction {
  Up,
  Down,
}

普通数字枚举编译后会生成包含正向映射和反向映射的对象。大致可以理解为:

ts 复制代码
Direction[Direction['Up'] = 0] = 'Up'
Direction[Direction['Down'] = 1] = 'Down'

2.2 const enum 会内联成员值

ts 复制代码
const enum Direction {
  Up = 0,
  Down = 1,
}

const direction = Direction.Up

在 TypeScript 的传统编译行为下,成员引用会被直接替换为常量值,通常不会生成完整的枚举运行时对象:

ts 复制代码
const direction = 0 /* Direction.Up */

这样可以减少生成的代码,但也带来了限制:不能依赖它一定存在可供外部访问的运行时对象。

Vue 官方编译器能够识别 <script setup> 中的 const enum,但 Vue SFC 的识别能力不能替代对整个构建链的验证。

2.3 Vite、isolatedModules 与跨模块限制

Vue + Vite 项目通常采用单文件转译方式处理 TypeScript。官方 Vue TypeScript 指南建议启用 isolatedModules,或者采用包含相应约束的 verbatimModuleSyntax 配置。

单文件转译器无法像完整的 TypeScript 编译器那样跨模块分析所有类型信息,因此 const enum 尤其需要谨慎。

主要风险包括:

  • 跨模块内联: 消费方可能需要枚举的实际成员值,但转译器不一定能获取这些值。
  • 类型声明发布: 发布到 npm 的声明文件如果包含环境枚举形式的 const enum,可能给使用不同编译配置的消费者带来问题。
  • 版本不一致: 消费方编译时内联的值,可能与运行时加载的依赖版本不一致。

因此,项目内部单一构建链下的 const enum,与公共库跨模块发布的 const enum,不应该采用相同的风险判断。

三、方案选型:enum、const enum 与 as const

3.1 三种方案的区别

对比项 enum const enum as const 对象
运行时对象 有 通常没有独立对象 有
模板访问 支持 受编译链影响 支持
类型用途 支持 支持 通过 typeof 推导
数字反向映射 支持 不适用独立对象访问 不支持
跨模块兼容性 较好 需要额外评估 较好
推荐场景 常规枚举 可控的内部优化场景 前端状态、常量集合

3.2 什么时候使用普通 enum?

当需要明确的枚举类型、运行时成员访问,或者希望通过数字枚举的反向映射获取成员名称时,可以选择普通 enum。

ts 复制代码
export enum OrderStatus {
  Pending = 0,
  Completed = 1,
}

它的优点是语义明确,且对运行时访问和跨模块导入的支持相对直接。

3.3 什么时候使用 as const?

对于 Vue 前端项目中的状态值、选项集合、接口字段映射等场景,as const 对象往往是更灵活的替代方案。

ts 复制代码
export const OrderStatus = {
  Pending: 'pending',
  Completed: 'completed',
} as const

export type OrderStatus =
  (typeof OrderStatus)[keyof typeof OrderStatus]

这里有两个不同的导出:

  • OrderStatus:运行时对象,可用于模板和业务逻辑。
  • OrderStatus 类型:通过对象值推导出的字面量联合类型,可用于参数、变量和接口定义。

使用方式:

html 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { OrderStatus } from '@/enums/order'

const status = ref(OrderStatus.Pending)
</script>

<template>
  <div v-if="status === OrderStatus.Completed">
    已完成
  </div>
</template>

在纯类型场景中,则可以写:

ts 复制代码
import type { OrderStatus } from '@/enums/order'

function updateStatus(status: OrderStatus) {
  // status 只能是 'pending' 或 'completed'
}

注意,as const 只影响 TypeScript 类型推导,不会自动对外部接口返回的数据进行运行时校验。

3.4 最终选型建议

  • 组件私有枚举: 可以直接在 <script setup lang="ts"> 中声明。
  • 跨模块共享枚举: 默认使用普通 enum,或者使用 as const 对象。
  • 需要运行时对象: 不要把 const enum 当作普通对象使用。
  • 公共 npm 包或多构建工具项目: 避免未经验证地发布环境 const enum。
  • 前端状态和接口常量: 优先考虑 as const 对象,便于同时获得运行时值和精确的字面量类型。

总结

选择 enum 时,核心不是追求最少的生成代码,而是明确三个边界:声明位置决定模块组织方式,编译语义决定运行时行为,使用场景决定最终选型。

对大多数 Vue 3 + TypeScript 项目而言,普通 enum 和 as const 对象已经可以覆盖绝大多数需求;只有在清楚掌握编译链、类型声明发布方式和运行时边界后,才值得引入 const enum。

相关推荐
ZhengEnCi1 小时前
12 种高级 UI 交互效果及对应 AI 提示词:从按压回弹到底部抽屉的物理感教学
前端·css·交互设计
IT_陈寒4 小时前
Redis大key删除为什么会导致服务卡顿?我的线上事故复盘
前端·人工智能·后端
用户2705077725245 小时前
MCP 最小 Server 实测:3 个 JSON-RPC 报错与修法
前端
用户499335025075 小时前
一人VIP账号多人共用:Web 代理里的会话共享怎么实现
前端
用户老李5 小时前
无线电自学教程 05:第一台收音机——从矿石机到 SDR
前端
IPIPD5 小时前
Node.js请求遇到503怎么重试?先解析Retry-After,再做有上限退避
前端
葡萄城技术团队5 小时前
dsh-spreadjs-editor 的集成链路:把 SpreadJS Designer 嵌进 Agent 工作台
前端
FlyWIHTSKY6 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js
码云之上6 小时前
10 颗一模一样的「Edit」:中后台 Web Agent 怎么找到对的那一行
前端·人工智能·浏览器
量化分析码农6 小时前
【Python量化多因子实战 04】因子暴露不是 alpha:市值与行业中性化实战
前端·后端