# enum-plus
**Repository Path**: chenming142/enum-plus
## Basic Information
- **Project Name**: enum-plus
- **Description**: 和原生 enum 一样,但更强大! 简介 enum-plus是一个增强版的枚举类库,完全兼容原生enum的基本用法,同时支持扩展显示文本、绑定到 UI 组件以及提供丰富的扩展方法,
- **Primary Language**: TypeScript
- **License**: MIT
- **Default Branch**: master
- **Homepage**: https://www.oschina.net/p/enum-plus
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2025-08-20
- **Last Updated**: 2025-09-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[English](./README.md) | [中文](./README.zh-CN.md) | [CHANGELOG](./CHANGELOG.md)
像原生 enum 一样,但更强大!
[](https://www.npmjs.com/package/enum-plus)
[](https://www.npmjs.com/package/enum-plus)
[](https://github.com/shijistar/enum-plus)
[](https://www.npmjs.com/package/enum-plus)
[](https://codecov.io/gh/shijistar/enum-plus)
[](https://www.npmjs.com/package/enum-plus)

⬇️ [简介](#简介) | [特性](#特性) | [安装](#安装) | [枚举定义](#枚举定义) | [API](#api) | [用法](#用法) | [命名规范建议](#命名规范建议) | [本地化](#本地化) | [全局扩展](#全局扩展) | [兼容性](#兼容性) | [常见问题](#常见问题) | [贡献](#贡献) ⬇️
## 简介
`enum-plus`是一个增强版的枚举类库,完全兼容原生`enum`的基本用法,同时支持扩展显示文本、绑定到 UI 组件以及提供丰富的扩展方法,是原生`enum`的一个直接替代品。它是一个轻量级、零依赖、100% TypeScript 实现的工具,适用于多种前端框架,并支持本地化。
枚举项扩展了显示名称后,可以与枚举值用来一键生成下拉框、复选框等组件。通过枚举的扩展方法,可以轻松遍历枚举项数组,获取某个枚举值的显示文本,判断某个值是否存在等。枚举项的显示文本支持本地化,可以根据当前语言环境返回对应的文本,这样可以使得枚举项的显示文本更加灵活,更加符合用户的需求。
还有哪些令人兴奋的特性呢?请继续探索吧!或者不妨先看下这个使用视频。
## 特性
- 完全兼容原生 `enum` 的用法
- 支持`number`、`string`等多种数据类型
- 增强的枚举项,支持自定义显示文本
- 内置`本地化`能力,枚举项文本可实现国际化,可与任何 i18n 库集成
- 支持枚举值转换为显示文本,代码更简洁
- 可扩展设计,允许在枚举项上添加自定义字段
- 支持将枚举绑定到 [Ant Design](https://ant-design.antgroup.com/components/overview-cn)、[ElementPlus](https://element-plus.org/zh-CN/component/select.html)、[Material-UI](https://mui.com/material-ui) 等 UI 库,一行代码枚举变下拉框
- 支持 Node.js 环境,支持服务端渲染(SSR)
- 零依赖,纯原生 JavaScript,可用于任何前端框架
- 100% TypeScript 实现,具有全面的类型推断能力
- 轻量(gzip 压缩后仅 2KB+)
## 安装
使用 npm 安装:
```bash
npm install enum-plus
```
使用 pnpm 安装:
```bash
pnpm add enum-plus
```
使用 bun 安装:
```bash
bun add enum-plus
```
或者使用 yarn:
```bash
yarn add enum-plus
```
## 枚举定义
本节展示了使用 `Enum` 函数初始化枚举的多种方式,你可以根据不同的使用场景选择最合适的方法
### 1. 基础格式,与原生枚举用法基本一致
```js
import { Enum } from 'enum-plus';
const WeekEnum = Enum({
Sunday: 0,
Monday: 1,
} as const);
WeekEnum.Monday; // 1
```
> `as const` 类型断言用于将枚举值变成字面量类型,类型更精确,否则它们将被作为`number`类型。如果你使用的是JavaScript,请删除`as const`。
### 2. 基础格式,String 类型
与第一种方式类似,只不过枚举值是字符串类型
```js
import { Enum } from 'enum-plus';
const WeekEnum = Enum({
Sunday: 'Sun',
Monday: 'Mon',
} as const);
WeekEnum.Monday; // 'Mon'
```
### 3. 标准格式(推荐)
为每个枚举项指定 `value` (枚举值) 和 `label`(显示文本)字段,这是最常用的格式,也是推荐的格式。这种格式允许你为每个枚举项设置显示文本,这些文本可以在UI组件中使用。
```js
import { Enum } from 'enum-plus';
const WeekEnum = Enum({
Sunday: { value: 0, label: '星期日' },
Monday: { value: 1, label: '星期一' },
} as const);
WeekEnum.Sunday; // 0
WeekEnum.label(0); // 星期日
```
- ### 4. Label-Only 格式
当你希望使用`key`作为枚举值时,这种方式比较有用,此时`value`和`key`的值相同,`label`是显示文本
```js
import { Enum } from 'enum-plus';
const WeekEnum = Enum({
Sunday: { label: '星期日' },
Monday: { label: '星期一' },
} as const);
WeekEnum.Sunday; // 'Sunday'
WeekEnum.label('Sunday'); // 星期日
```
### 5. 数组格式
数组格式在需要动态创建枚举时很有用,例如从 API 获取数据中动态创建一个枚举。这种方式还允许[自定义字段映射](#自定义字段映射),这增加了灵活性,可以适配不同的数据格式
```js
import { Enum } from 'enum-plus';
const petTypes = await getPetsData();
// [ { value: 1, key: 'dog', label: '狗' },
// { value: 2, key: 'cat', label: '猫' },
// { value: 3, key: 'rabbit', label: '兔子' } ];
const PetTypes = Enum(petTypes);
```
### 6. 原生枚举格式
如果你已经有一个原生的枚举,你可以直接传递给`Enum`函数,它会自动转换为增强版的枚举,这样可以借用原生枚举的`枚举值自动递增`特性
```ts
import { Enum } from 'enum-plus';
enum init {
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
const WeekEnum = Enum(init);
WeekEnum.Sunday; // 0
WeekEnum.Monday; // 1
WeekEnum.Saturday; // 6
WeekEnum.label('Sunday'); // Sunday
```
## API
### 💎 拾取枚举值
`Enum.XXX`
像原生`enum`一样,直接拾取一个枚举值
```js
WeekEnum.Sunday; // 0
WeekEnum.Monday; // 1
```
---
### 💎 items
`{ value, label, key, raw }[]`
获取一个包含全部枚举项的只读数组,可以方便地遍历枚举项。由于符合 [Ant Design](https://ant-design.antgroup.com/components/select-cn#usage-upgrade) 组件的数据规范,因此支持将枚举一键转换成下拉框、复选框等组件,只需要一行代码,更多详情可以参考后面的例子
---
### 💎 keys
`string[]`
获取一个包含全部枚举项`key`的只读数组
---
### 💎 label
**_\[方法]_** `label(keyOrValue?: string | number): string | undefined`
根据某个枚举值或枚举 key,获取该枚举项的显示文本。如果设置了本地化,则会返回本地化后的文本。
```js
WeekEnum.label(1); // 星期一
WeekEnum.label('Monday'); // 星期一
```
---
### 💎 key
**_\[方法]_** `key(value?: string | number): string | undefined`
根据枚举值获取该枚举项的 key,如果不存在则返回`undefined`
```js
WeekEnum.key(1); // 'Monday'
```
---
### 💎 has
**_\[方法]_** `has(keyOrValue?: string | number): boolean`
判断某个枚举项(值或 key)是否存在
```js
WeekEnum.has(1); // true
WeekEnum.has('Sunday'); // true
WeekEnum.has(9); // false
WeekEnum.has('Birthday'); // false
```
---
### 💎 toSelect
**_\[方法]_** `toSelect(config?: OptionsConfig): {value, label}[]`
`toSelect`与`items`相似,都是返回一个包含全部枚举项的数组。区别是,`toSelect`返回的元素只包含`label`和`value`两个字段,同时,`toSelect`方法支持在数组头部插入一个默认元素,一般用于下拉框等组件的默认选项,表示全部、无值或不限等,当然你也能够自定义这个默认选项
---
### 💎 toMenu
**_\[方法]_** `toMenu(): { key, label }[]`
生成一个对象数组,可以绑定给 [Ant Design](https://ant-design.antgroup.com/components/menu-cn#itemtype) 的`Menu`、`Dropdown`等组件
```js
import { Menu } from 'antd';
;
```
数据格式为:
```js
[
{ key: 0, label: '星期日' },
{ key: 1, label: '星期一' },
];
```
---
### 💎 toFilter
**_\[方法]_** `toFilter(): { text, value }[]`
生成一个对象数组,可以直接传递给 [Ant Design](https://ant-design.antgroup.com/components/table-cn#table-demo-head) Table 组件的列配置,在表头中显示一个下拉筛选框,用来过滤表格数据
数据格式为:
```js
[
{ text: '星期日', value: 0 },
{ text: '星期一', value: 1 },
];
```
---
### 💎 toValueMap
**_\[方法]_** `toValueMap(): Record`
生成一个符合 [Ant Design Pro](https://pro-components.antdigital.dev/components/schema#valueenum) 规范的枚举集合对象,可以传递给 `ProFormField`、`ProTable` 等组件。
数据格式为:
```js
{
0: { text: '星期日' },
1: { text: '星期一' },
}
```
---
### 💎 raw
**_\[方法重载^1]_** `raw(): Record`
**_\[方法重载^2]_** `raw(keyOrValue: V | K): T[K]`
第一个重载方法,返回枚举集合的初始化对象,即用来初始化 Enum 原始 init 对象。
第二个重载方法,用来处理单个枚举项,根据获取单个枚举项的原始初始化对象。
这个方法主要作用是,用来获取枚举项的自定义字段,支持无限扩展字段
```js
const WeekEnum = Enum({
Sunday: { value: 0, label: '星期日', happy: true },
Monday: { value: 1, label: '星期一', happy: false },
} as const);
WeekEnum.raw(0).happy // true
WeekEnum.raw(0); // { value: 0, label: '星期日', happy: true }
WeekEnum.raw('Monday'); // { value: 1, label: '星期一', happy: false }
WeekEnum.raw(); // { Sunday: { value: 0, label: '星期日', happy: true }, Monday: { value: 1, label: '星期一', happy: false } }
```
---
### 💎 name
`string`
枚举类型的显示名称。在创建枚举时,可以通过传入一个可选的 `name` 参数来为枚举类型命名。这个名称可以是一个普通字符串,也可以是一个本地化键值,以支持国际化文本。关于本地化的更多详情,请参考[本地化](#本地化)章节。
> 在UI组件中,枚举通常用来作为数据源,生成下拉框表单项,或在表格单元格中显式枚举成员文本。而对应的表单项标签或列标题就是枚举类型的名称。通过使用`name`,我们可以集中管理枚举名称,和枚举成员的名称,也更方便使用。
```js
const WeekEnum = Enum({
Sunday: { value: 0, label: '星期日', happy: true },
Monday: { value: 1, label: '星期一', happy: false },
} as const, {
name: 'i18n.enums.week', // 可以是一个本地化键值
});
WeekEnum.name; // 周
WeekEnum.label(0); // 星期日
WeekEnum.label(1); // 星期一
```
---
### ⚡️ valueType **_\[TypeScript ONLY]_**
`value1 | value2 | ...`
在 TypeScript 中,提供了一个包含所有枚举值的联合类型,用于缩小变量或组件属性的数据类型。这种类型替代了像 `number` 或 `string` 这样宽泛的原始类型,使用精确的值集合,防止无效赋值,同时提高代码可读性和编译时类型安全性。
```typescript
type WeekValues = typeof WeekEnum.valueType; // 0 | 1
const weekValue: typeof WeekEnum.valueType = 1; // ✅ 类型正确,1 是一个有效的周枚举值
const weeks: (typeof WeekEnum.valueType)[] = [0, 1]; // ✅ 类型正确,0 和 1 是有效的周枚举值
const badWeekValue: typeof WeekEnum.valueType = 8; // ❌ 类型错误,8 不是一个有效的周枚举值
const badWeeks: (typeof WeekEnum.valueType)[] = [0, 8]; // ❌ 类型错误,8 不是一个有效的周枚举值
```
> 注意,这只是一个 TypeScript 类型,只能用来约束类型,不可在运行时调用,运行时调用会抛出异常
---
### ⚡️ keyType **_\[TypeScript ONLY]_**
`key1 | key2 | ...`
与`valueType`类似,获取一个包含全部枚举 `key` 的联合类型
```typescript
type WeekKeys = typeof WeekEnum.keyType; // 'Sunday' | 'Monday'
const weekKey: typeof WeekEnum.keyType = 'Monday';
const weekKeys: (typeof WeekEnum.keyType)[] = ['Sunday', 'Monday'];
```
> 注意,这只是一个 TypeScript 类型,只能用来约束类型,不可在运行时调用,运行时调用会抛出异常
---
### ⚡️ rawType **_\[TypeScript ONLY]_**
`{ value: V, label: string, [...] }`
获取初始化整个枚举集合的原始类型,即用来创建枚举集合的对象。
与无参数的`raw`方法类似,只不过`raw`是一个运行时方法,而`rawType`是一个约束类型
> 注意,这只是一个 TypeScript 类型,只能用来约束类型,不可在运行时调用,运行时调用会抛出异常
---
## 用法
#### 拾取枚举值,与原生枚举用法一致
```js
const WeekEnum = Enum({
Sunday: { value: 0, label: '星期日' },
Monday: { value: 1, label: '星期一' },
} as const);
WeekEnum.Sunday; // 0
WeekEnum.Monday; // 1
```
---
#### 枚举项支持 Jsdoc 注释,启用代码智能提示
在代码编辑器中,将光标悬停在枚举项上,即可显示关于该枚举项的详细 Jsdoc 注释,而不必再转到枚举定义处查看
```js
const WeekEnum = Enum({
/** 星期日 */
Sunday: { value: 0, label: '星期日' },
/** 星期一 */
Monday: { value: 1, label: '星期一' },
} as const);
WeekEnum.Monday; // 将光标悬浮在 Monday 上
```

可以看到,不但提示了枚举项的释义,还有枚举项的值,无需跳转离开当前光标位置,在阅读代码时非常方便
---
#### 获取包含全部枚举项的数组
```js
WeekEnum.items; // 输出如下:
// [
// { value: 0, label: '星期日', key: 'Sunday', raw: { value: 0, label: '星期日' } },
// { value: 1, label: '星期一', key: 'Monday', raw: { value: 1, label: '星期一' } }
// ]
```
---
#### 获取第一个枚举值
```js
WeekEnum.items[0].value; // 0
```
---
#### 检查一个值是否一个有效的枚举值
```js
WeekEnum.has(1); // true
WeekEnum.items.some((item) => item.value === 1); // true
1 instanceof WeekEnum; // true
```
---
#### `instanceof` 操作符
```js
1 instanceof WeekEnum; // true
'1' instanceof WeekEnum; // true
'Monday' instanceof WeekEnum; // true
```
---
#### 支持遍历枚举项数组,但不可修改
```js
WeekEnum.items.length; // 2
WeekEnum.items.map((item) => item.value); // [0, 1],✅ 可遍历
WeekEnum.items.forEach((item) => {}); // ✅ 可遍历
for (const item of WeekEnum.items) {
// ✅ 可遍历
}
WeekEnum.items.push({ value: 2, label: '星期二' }); // ❌ 不可修改
WeekEnum.items.splice(0, 1); // ❌ 不可修改
WeekEnum.items[0].label = 'foo'; // ❌ 不可修改
```
---
#### 枚举值(或key)转换为显示文本
```js
WeekEnum.label(1); // 星期一,
WeekEnum.label(WeekEnum.Monday); // 星期一
WeekEnum.label('Monday'); // 星期一
```
---
#### 枚举值转换为key
```js
WeekEnum.key(1); // 'Monday'
WeekEnum.key(WeekEnum.Monday); // 'Monday'
WeekEnum.key(9); // undefined, 不存在此枚举项
```
---
#### 添加扩展字段,不限数量
```js
const WeekEnum = Enum({
Sunday: { value: 0, label: '星期日', active: true, disabled: false },
Monday: { value: 1, label: '星期一', active: false, disabled: true },
} as const);
WeekEnum.raw(0).active // true
WeekEnum.raw(WeekEnum.Sunday).active // true
WeekEnum.raw('Sunday').active // true
```
---
#### 转换成 UI 组件
- `Enum.items` 可以直接作为组件的数据源(以 Select 组件为例)
**React相关框架**
[Ant Design](https://ant-design.antgroup.com/components/select-cn) | [Arco Design](https://arco.design/react/components/select)
Select
```tsx
import { Select } from 'antd';
;
```
[Material-UI](https://mui.com/material-ui/react-select/) Select
```tsx
import { MenuItem, Select } from '@mui/material';
;
```
[Kendo UI](https://www.telerik.com/kendo-react-ui/components/dropdowns/dropdownlist) Select
```tsx
import { DropDownList } from '@progress/kendo-react-dropdowns';
;
```
**Vue相关框架**
[ElementPlus](https://element-plus.org/zh-CN/component/select.html) Select
```tsx
```
[Ant Design Vue](https://antdv.com/components/select-cn) | [Arco Design](https://arco.design/vue/component/select) Select
```tsx
```
[Vuetify](https://vuetifyjs.com/zh-Hans/components/selects) Select
```tsx
```
**Angular相关框架**
[Angular Material](https://material.angular.io/components/select/overview) Select
```jsx
@for (item of WeekEnum.items; track item.value) {
{{ item.label }}
}
```
[NG-ZORRO](https://ng.ant.design/components/select/zh) Select
```jsx
@for (item of WeekEnum.items; track item.value) {
{{ item.label }}
}
```
- `toSelect`方法与`items`类似,但允许在头部增加一个默认选项。默认选项可以是一个布尔值,也可以是一个自定义对象。
- 如果是布尔值,则默认选项为`{ value: '', label: 'All' }`,显示名称只支持英文。如果希望支持本地化,请在本地化方法中解析并处理`enum-plus.options.all`这个内置资源。关于本地化的更多详情,请参考[本地化](#本地化)章节
- 如果是一个对象,则可以自定义默认选项的值和显示文本,显示文本会自动支持本地化
```tsx
// [
// { value: '', label: 'All' },
// { value: 0, label: '星期日' },
// { value: 1, label: '星期一' }
// ]
// 自定义头部默认选项
```
- `toMenu`方法可以为 [Ant Design](https://ant-design.antgroup.com/components/menu-cn#itemtype) `Menu`、`Dropdown` 等组件生成数据源,格式为:`{ key: number|string, label: string } []`
```tsx
import { Menu } from 'antd';
;
```
- `toFilter`方法可以生成一个对象数组,为表格绑定`列筛选`功能,列头中显示一个下拉筛选框,用来过滤表格数据。对象结构遵循 [Ant Design](https://ant-design.antgroup.com/components/table-cn#table-demo-head) 的数据规范,格式为:`{ text: string, value: number|string } []`
```tsx
import { Table } from 'antd';
const columns = [
{
title: 'week',
dataIndex: 'week',
filters: WeekEnum.toFilter(),
},
];
// 在表头中显示下拉筛选项
;
```
- `toValueMap`方法可以为 [Ant Design Pro](https://pro-components.antdigital.dev/components/schema#valueenum) 的`ProFormFields`、`ProTable`等组件生成数据源,这是一个类似 Map 的数据结构,格式为:`{ [key: number|string]: { text: string } }`
```tsx
import { ProFormCheckbox, ProFormRadio, ProFormSelect, ProFormTreeSelect } from '@ant-design/pro-components';
; // 下拉框
; // 复选框
; // 单选框
; // 树选择
```
---
#### 两个枚举合并(或者扩展某个枚举)
```js
const myWeek = Enum({
...WeekEnum.raw(),
Friday: { value: 5, label: '星期五' },
Saturday: { value: 6, label: '星期六' },
});
```
---
#### 使用枚举值序列来缩小 `number` 取值范围 _\[TypeScript ONLY]_
使用`valueType`类型约束,你可以将变量类型从宽泛的基本类型(如`number`或`string`)精确缩小为枚举值的联合类型。这种类型缩窄不仅能在编译时防止无效赋值,还能增强代码的可读性和自文档化能力,同时提供更强的类型安全保障
```typescript
const weekValue: number = 8; // 👎 任意数字都可以赋值给周枚举,即使错误的
const weekName: string = 'Birthday'; // 👎 任意字符串都可以赋值给周枚举,即使错误的
const goodWeekValue: typeof WeekEnum.valueType = 1; // ✅ 类型正确,1 是一个有效的周枚举值
const goodWeekName: typeof WeekEnum.keyType = 'Monday'; // ✅ 类型正确,'Monday' 是一个有效的周枚举名
const badWeekValue: typeof WeekEnum.valueType = 8; // ❌ 类型错误,8 不是一个有效的周枚举值
const badWeekName: typeof WeekEnum.keyType = 'Birthday'; // ❌ 类型错误,'Birthday' 不是一个有效的周枚举名
type FooProps = {
value?: typeof WeekEnum.valueType; // 👍 组件属性类型约束,可以防止错误赋值,还能智能提示有效值有哪些
names?: (typeof WeekEnum.keyType)[]; // 👍 组件属性类型约束,可以防止错误赋值,还能智能提示有效值有哪些
};
```
---
#### 自定义字段映射
在 [5. 数组格式](#5-数组格式) 章节中,介绍了可以通过后端动态数据来构建枚举,但是很可能动态数据的字段名并不是`value`、`label`、`key`,而是其它的字段名。这时你可以传入一个自定义选项,把这些映射到其它字段名上
```js
import { Enum } from 'enum-plus';
const petTypes = await getPetsData();
// [ { id: 1, code: 'dog', name: '狗' },
// { id: 2, code: 'cat', name: '猫' },
// { id: 3, code: 'rabbit', name: '兔子' } ];
const PetTypes = Enum(petTypes, {
getValue: 'id',
getLabel: 'name',
getKey: 'code', // getKey可选
});
WeekEnum.items; // 输出如下:
// [ { value: 1, label: '狗', key: 'dog' },
// { value: 2, label: '猫', key: 'cat' },
// { value: 3, label: '兔子', key: 'rabbit' } ]
```
在上面的例子中,`getValue`、`getLabel`、`getKey` 还可以是一个函数,用来处理更复杂的业务逻辑,比如:
```js
const PetTypes = Enum(petTypes, {
getValue: (item) => item.id,
getLabel: (item) => `${item.name} (${item.code})`,
getKey: (item) => item.code,
});
```
---
#### 命名冲突?
这里为枚举使用添加一些边界情况,从上面的用例中可以看到,我们可以通过 `WeekEnum.XXX` 来快捷访问枚举项,但是万一枚举项的 key 与枚举方法命名冲突怎么办?
我们知道枚举类型上还存在 `label`、`key`、`toSelect` 等方法,如果与某个枚举项重名,枚举项的值优先级更高,会覆盖掉这些方法。但不用担心,你可以在 `items` 下访问到它们。请参考下面的代码示例:
```js
const WeekEnum = Enum({
foo: { value: 1 },
bar: { value: 2 },
keys: { value: 3 }, // 命名冲突
label: { value: 4 }, // 命名冲突
} as const);
WeekEnum.keys; // 3,枚举项优先级更高,会覆盖掉方法
WeekEnum.label; // 4,枚举项优先级更高,会覆盖掉方法
// 可以通过 Enum.items 访问到这些方法 🙂
WeekEnum.items.keys; // ['foo', 'bar', 'keys', 'label']
WeekEnum.items.label(1); // 'foo'
```
更极端一些,万一`items`与枚举项命名冲突怎么办?放心,你仍然可以通过别名字段访问到`items`数组。参考下面的示例:
```js
import { ITEMS } from 'enum-plus';
const WeekEnum = Enum({
foo: { value: 1 },
bar: { value: 2 },
items: { value: 3 }, // 命名冲突
} as const);
WeekEnum.items; // 3,枚举项优先级更高,会覆盖掉 items
WeekEnum[ITEMS]; // ITEMS 是一个别名Symbol
// [
// { value: 1, key: 'foo', label: 'foo' },
// { value: 2, key: 'bar', label: 'bar' },
// { value: 3, key: 'items', label: 'items' }
// ]
// 等价于原来的 WeekEnum.items 🙂
```
---
## 命名规范建议
1. **枚举类型命名:** 采用 `PascalCase` 大驼峰命名法,并以 `Enum` 作为后缀,如 _WeekEnum_、_ColorEnum_ 等。
2. **枚举成员命名:**使用 `PascalCase` 大驼峰命名法,如 _WeekEnum.Sunday_、_ColorEnum.Red_ 等。此命名方式突显了枚举成员的不可变性与静态特性,且在IDE智能提示中会在顶部显示,更方便拾取。
3. **语义明确:** 确保枚举和成员名称具有清晰的语义,良好的语义命名能够自解释代码意图,降低理解成本。
4. **单一职责原则:** 每个枚举类型应专注表达一组高内聚的相关常量,避免不同枚举类型之间的职责重叠。
5. **提供JSDoc注释:** 为每个枚举项添加 Jsdoc 注释,说明其含义和用途。完善的JSDoc文档能在IDE中提供悬停提示,提升代码阅读体验。同样也建议为枚举类添加注释。
6. **国际化架构:** 建议从开始就搭建国际化架构,可集成本库提供的 [本地化](#本地化) 机制。预先设计的国际化方案能够避免后期重构的高成本,并使应用更易于扩展到全球市场。
---
## 本地化
`enum-plus` 本身不内置国际化能力,但支持通过 `localize` 可选参数传入一个自定义方法,来实现本地化文本的转化。这是一个非常灵活的方案,这使你能够实现自定义的本地化函数,根据当前的语言环境将枚举的 `label` 值转换为适当的翻译文本。语言状态管理仍由你自己负责,你的 `localize` 方法决定返回哪种本地化文本。对于生产环境的应用程序,我们强烈建议使用成熟的国际化库(如 `i18next`),而不是创建自定义解决方案。
以下是一个简单的示例,仅供参考。请注意,第一种方法由于缺乏灵活性,不建议在生产环境中使用,它仅用于演示基本概念。请考虑使用第二种及后面的示例。
```tsx
import { Enum } from 'enum-plus';
import i18next from 'i18next';
import Localize from './Localize';
let lang = 'zh-CN';
const setLang = (l: string) => {
lang = l;
};
// 👎 这不是一个好例子,仅供演示,不建议生产环境使用
const sillyLocalize = (content: string) => {
if (lang === 'zh-CN') {
switch (content) {
case 'enum-plus.options.all':
return '全部';
case 'weekDays.name':
return '周';
case 'week.sunday':
return '星期日';
case 'week.monday':
return '星期一';
default:
return content;
}
} else {
switch (content) {
case 'enum-plus.options.all':
return 'All';
case 'weekDays.name':
return 'Week';
case 'week.sunday':
return 'Sunday';
case 'week.monday':
return 'Monday';
default:
return content;
}
}
};
// 👍 建议使用 i18next 或其他国际化库
const i18nLocalize = (content: string | undefined) => i18next.t(content);
// 👍 或者封装成一个基础组件
const componentLocalize = (content: string | undefined) => ;
const WeekEnum = Enum(
{
Sunday: { value: 0, label: 'week.sunday' },
Monday: { value: 1, label: 'week.monday' },
} as const,
{
localize: sillyLocalize,
// localize: i18nLocalize, // 👍 推荐使用i18类库
// localize: componentLocalize, // 👍 推荐使用组件形式
name: 'weekDays.name', // 可选,枚举类型的本地化名称,一般显示为表格列或表单字段标题
}
);
setLang('zh-CN');
WeekEnum.label(1); // 星期一
WeekEnum.name; // 周
setLang('en-US');
WeekEnum.label(1); // Monday
WeekEnum.name; // Week
```
当然,每个枚举类型都这样设置可能比较繁琐,`enum-plus` 提供了一种全局设置方案,可以通过 `Enum.localize` 全局方法,来全局设置本地化。如果两者同时存在,单个枚举的设置会覆盖全局设置。
```js
Enum.localize = i18nLocalize;
```
---
## 全局扩展
虽然 `Enum` 提供了一套全面的内置方法,但如果这些还不能满足你的需求,你可以使用 `Enum.extends` API 扩展其功能,添加自定义方法。这些扩展会全局应用于所有枚举实例,包括在扩展应用之前创建的实例,并且会立即生效,无需任何其它设置。
_**App.ts**_
```tsx
Enum.extends({
toMySelect(this: ReturnType) {
return this.items.map((item) => ({ value: item.value, title: item.label }));
},
reversedItems(this: ReturnType) {
return this.items.reverse();
},
});
WeekEnum.toMySelect(); // [{ value: 0, title: '星期日' }, { value: 1, title: '星期一' }]
```
如果你在使用 TypeScript,你可能需要再扩展一下枚举类型声明,这样可以获得更好的类型提示。在你的项目中创建或编辑一个声明文件(例如 `global.d.ts`),并在其中扩展全局类型。此文件可以放在项目的根目录或任意目录下,只要确保 TypeScript 能够找到它
_**global.d.ts**_
```tsx
import type { EnumItemInit } from 'enum-plus';
import type { EnumItemClass } from 'enum-plus/lib/enum-item';
declare global {
export interface EnumExtension {
toMySelect: () => { value: V; title: string }[];
reversedItems: () => EnumItemClass, K, V>[];
}
}
```
请注意,你不是必须导入`EnumItemInit`和`EnumItemClass`这些类型,这些仅在这个示例中被使用,为了添加更友好的类型提示。
`EnumExtension`接口是一个泛型接口,它接受三个类型参数,分别是:
- `T`: 枚举类型的初始化对象
- `K`: 枚举项的键值
- `V`: 枚举项的值
如果你希望在扩展方法中提供更友好的类型提示,你或许可能需要使用到这些类型参数,但这些都是可选的,如果你不需要,可以直接省略掉它们
---
## 兼容性
enum-plus 提供了完善的兼容性支持。
- **浏览器环境**:
- **现代打包工具**:对于支持 [exports](https://nodejs.org/api/packages.html#exports-sugar) 字段的打包工具(如 Webpack 5+、Vite、Rollup),enum-plus 的目标是 **`ES2020`**。如果需要更广泛的浏览器支持,可以在构建过程中使用 `@babel/preset-env` 转译为更早期的语法。
- **旧版打包工具**:对于不支持 `exports` 字段的工具(如 Webpack 4),enum-plus 会自动回退到 `main` 字段的入口点,其目标是 **`ES2016`**。
- **Polyfill 策略**:为了最小化包的体积,enum-plus 不包含任何 polyfill。如果需要支持旧版浏览器,可以引入以下内容:
- `core-js`
- 配置适当的 `@babel/preset-env` 和 `useBuiltIns` 设置
- 其他替代的 polyfill 实现
-
- **Node.js 兼容性**:enum-plus 需要至少 **`ES2016`** 的特性,兼容 Node.js `v7.x` 及以上版本。
---
## 常见问题
### 1. 如何基于 i18next 实现国际化?
请参考下面的示例代码:
_main.tsx_
```tsx
import { createRoot } from 'react-dom/client';
import { Enum } from 'enum-plus';
import i18next from 'i18next';
import App from './App';
import Locale from './components/Locale';
import LocaleProvider from './components/LocaleProvider';
import enUS from './locales/en-US';
import zhCN from './locales/zh-CN';
i18next.init({
lng: localStorage.getItem('my_lang'),
fallbackLng: 'en-US',
supportedLngs: ['en-US', 'zh-CN'],
resources: {
'en-US': { translation: enUS },
'zh-CN': { translation: zhCN },
},
});
i18next.on('languageChanged', (lang) => {
localStorage.setItem('my_lang', lang);
});
// 👀 这里是关键代码,通过 Enum.localize 方法全局设置,使用 Locale 组件来输出本地化文本
Enum.localize = (key?: string) => ;
const root = createRoot(document.getElementById('root'));
root.render(
);
```
_components/LocaleProvider.tsx_
```tsx
import type { FC, ReactNode } from 'react';
import { createContext, useEffect, useState } from 'react';
import i18next from 'i18next';
export const LocaleContext = createContext<{
lang: string;
setLang: (lang: string) => void;
}>({});
const LocaleProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [lang, setLang] = useState(i18next.language);
useEffect(() => {
i18next.changeLanguage(lang);
}, [lang]);
return {children};
};
export default LocaleProvider;
```
_components/Locale.tsx_
```tsx
import { useContext } from 'react';
import i18next from 'i18next';
import { LocaleContext } from './LocaleProvider';
export default function Localize({ value }: { value: string }) {
const { lang } = useContext(LocaleContext);
return <>{i18next.t(value, { lng: lang })}>;
}
```
### 2. 实现国际化后,为什么antd下拉框的搜索功能失效了?
这是因为 antd 下拉框的内置搜索功能是基于 `label` 值来实现的,只能支持常规字符串。而支持了国际化后,`label` 实际返回的是一个组件,而不是常规字符串,因此 antd 无法正确进行字符串匹配。解决方法是可以给枚举扩展一个 `filterOption` 方法,帮助 Select 组件自定义搜索功能,这样就可以正确地支持搜索功能了。
参考下面的示例代码:
```tsx
import { Select } from 'antd';
import { Enum, type EnumItemClass } from 'enum-plus';
Enum.extends({
filterOption: (search?: string, option?: EnumItemClass) => {
const label = $t(option?.raw?.label ?? '') ?? option?.value;
return !search || label?.toUpperCase().includes(search.toUpperCase());
},
});
// ;
```
## 贡献
如果你想为这个项目做出贡献,请遵循仓库中的 [CONTRIBUTING](CONTRIBUTING.md) 指南。 欢迎提交问题、拉取请求或改进建议。我们非常感谢你的贡献!
如果你发现安全问题,请遵循 [安全策略](SECURITY.md) 来负责任地报告。
如果你觉得这个项目有用,请考虑在 GitHub 上给它一个星标 ⭐️。这有助于其他人发现这个项目,并鼓励我们继续开发。