和原生 enum 一样,但更强大!
简介
enum-plus
是一个增强版的枚举类库,完全兼容原生enum
的基本用法,同时支持扩展显示文本、绑定到 UI 组件以及提供丰富的扩展方法,是原生enum
的一个直接替代品。它是一个轻量级、零依赖、100% TypeScript 实现的工具,适用于多种前端框架,并支持本地化。
枚举项列表可以用来一键生成下拉框、复选框等组件,可以轻松遍历枚举项数组,获取某个枚举值的显示文本,判断某个值是否存在等。支持本地化,可以根据当前语言环境返回对应的文本,轻松满足国际化的需求。
还有哪些令人兴奋的特性呢?请继续探索吧!或者不妨先看下这个使用视频。
特性
- 兼容原生
enum
的用法 - 支持
number
、string
等多种数据类型 - 支持枚举项扩展显示文本
- 支持本地化显示文本,可以使用任意国际化类库
- 支持枚举值转换为显示文本,代码更简洁
- 枚举项支持扩展任意个自定义字段
- 支持将枚举绑定到 Ant Design、ElementPlus、Material-UI 或任意其它组件库,只要一行代码
- 支持 Node.js 环境,支持服务端渲染(SSR)
- 零依赖,纯原生 JavaScript,可以应用在任意前端框架中
- 100% TypeScript 实现,支持类型推断
- 轻量(gzip 压缩后仅 1KB+)
安装
npm install enum-plus
枚举定义
构造一个枚举,枚举值支持 number
和 string
两种类型
- 基础用法(与原生枚举用法基本一致)
import { Enum } from 'enum-plus';
const Week = Enum({
Sunday: 0,
Monday: 1,
} as const);
Week.Monday; // 1
- 【标准用法】:包含 Key、Value,以及显示文本
import { Enum } from 'enum-plus';
const Week = Enum({
Sunday: { value: 0, label: '星期日' }, // 此示例不考虑本地化
Monday: { value: 1, label: '星期一' }, // 此示例不考虑本地化
} as const);
Week.Monday; // 1
Week.label(1); // 星期一
- 支持动态数组构建枚举
有时候我们需要使用接口返回的数据,动态创建一个枚举,这时可以采用数组的方式来初始化枚举
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 PetTypesEnum = Enum(petTypes);
API
💎 基本用法
像原生enum
一样,直接拾取一个枚举值
Week.Sunday; // 0
Week.Monday; // 1
{ value, label, key, raw }[]
获取一个包含全部枚举项的只读数组,可以方便地遍历枚举项。由于符合 Ant Design 组件的数据规范,因此支持将枚举一键转换成下拉框、复选框等组件,只需要一行代码,更多详情可以参考后面的例子
💎 keys string[]
获取一个包含全部枚举项Key
的只读数组
💎 label label(keyOrValue?: string | number): string | undefined
[方法] 根据某个枚举值或枚举 Key,获取该枚举项的显示文本。如果设置了本地化,则会返回本地化后的文本。
Week.label(1); // 星期一
Week.label('Monday'); // 星期一
💎 key key(value?: string | number): string | undefined
[方法] 根据枚举值获取该枚举项的 Key,如果不存在则返回undefined
Week.key(1); // 'Monday'
💎 has has(keyOrValue?: string | number): boolean
[方法] 判断某个枚举项(值或 Key)是否存在
Week.has(1); // true
Week.has('Sunday'); // true
Week.has(9); // false
Week.has('Birthday'); // false
💎 toSelect toSelect(config?: OptionsConfig): {value, label}[]
[方法] toSelect
与items
相似,都是返回一个包含全部枚举项的数组。区别是,toSelect
返回的元素只包含label
和value
两个字段,同时,toSelect
方法支持在数组头部插入一个默认元素,一般用于下拉框等组件的默认选项,表示全部、无值或不限等,当然你也能够自定义这个默认选项
💎 toMenu toMenu(): { key, label }[]
[方法] 生成一个对象数组,可以绑定给 Ant Design 的Menu
、Dropdown
等组件
import { Menu } from 'antd';
<Menu items={Week.toMenu()} />;
数据格式为:
[
{ key: 0, label: '星期日' },
{ key: 1, label: '星期一' },
];
💎 toFilter toFilter(): { text, value }[]
[方法] 生成一个对象数组,可以直接传递给 Ant Design Table 组件的列配置,在表头中显示一个下拉筛选框,用来过滤表格数据
数据格式为:
[
{ text: '星期日', value: 0 },
{ text: '星期一', value: 1 },
];
💎 toValueMap toValueMap(): Record<V, { text: string }>
[方法] 生成一个符合 Ant Design Pro 规范的枚举集合对象,可以传递给 ProFormField
、ProTable
等组件。
数据格式为:
{
0: { text: '星期日' },
1: { text: '星期一' },
}
💎 raw
raw(): Record<K, T[k]>
raw(keyOrValue: V | K): T[K]
第一个重载方法,返回枚举集合的初始化对象,即用来初始化 Enum 原始 init 对象。
第二个重载方法,用来处理单个枚举项,根据枚举值或枚举 Key 获取该枚举项的原始初始化对象,也就是说第二个方法是第一个方法返回值的一部分。另外,如果在枚举项上添加了额外的扩展字段的话,也可以用这种方式获取到
const Week = Enum({
Sunday: { value: 0, label: '星期日' },
Monday: { value: 1, label: '星期一' },
} as const);
Week.raw(); // { Sunday: { value: 0, label: '星期日' }, Monday: { value: 1, label: '星期一' } }
Week.raw(0); // { value: 0, label: '星期日' }
Week.raw('Monday'); // { value: 1, label: '星期一' }
用法
- 拾取枚举值,与原生枚举用法一致
const Week = Enum({
Sunday: { value: 0, label: '星期日' },
Monday: { value: 1, label: '星期一' },
} as const);
Week.Sunday; // 0
Week.Monday; // 1
- 获取包含全部枚举项的数组
Week.items; // 输出如下:
// [
// { value: 0, label: '星期日', key: 'Sunday', raw: { value: 0, label: '星期日' } },
// { value: 1, label: '星期一', key: 'Monday', raw: { value: 1, label: '星期一' } }
// ]
- 获取第一个枚举值
Week.items[0].value; // 0
- 检查一个值是否一个有效的枚举值
Week.has(1); // true
Week.items.some(item => item.value === 1); // true
1 instance of Week; // true
instanceof
操作符
1 instance of Week // true
"1" instance of Week // true
"Monday" instance of Week // true
- 支持遍历枚举项数组,但不可修改
Week.items.length; // 2
Week.items.map((item) => item.value); // [0, 1],✅ 可遍历
Week.items.forEach((item) => {}); // ✅ 可遍历
for (let item of Week.items) {
// ✅ 可遍历
}
Week.items.push({ value: 2, label: '星期二' }); // ❌ 不可修改
Week.items.splice(0, 1); // ❌ 不可修改
Week.items[0].label = 'foo'; // ❌ 不可修改
- 枚举值(或Key)转换为显示文本
Week.label(1); // 星期一,
Week.label(Week.Monday); // 星期一
Week.label('Monday'); // 星期一
- 枚举值转换为Key
Week.key(1); // 'Monday'
Week.key(Week.Monday); // 'Monday'
Week.key(9); // undefined, 不存在此枚举项
- 添加扩展字段,不限数量
const Week = Enum({
Sunday: { value: 0, label: '星期日', active: true, disabled: false },
Monday: { value: 1, label: '星期一', active: false, disabled: true },
} as const);
Week.raw(0).active // true
Week.raw(Week.Sunday).active // true
Week.raw('Sunday').active // true
- 转换成 Select 组件
items
可以直接作为组件的数据源(以 Select 组件为例)
Ant Design | Arco Design Select
import { Select } from 'antd';
<Select options={Week.items} />;
Material-UI Select
import { MenuItem, Select } from '@mui/material';
{Week.items.map((item) => (
<MenuItem key={item.value} value={item.value}>
{item.label}
</MenuItem>
))};
Kendo UI Select
import { DropDownList } from '@progress/kendo-react-dropdowns';
<DropDownList data={Week.items} textField="label" dataItemKey="value" />;
ElementPlus Select
<el-option v-for="item in Week.items" v-bind="item" />
Ant Design Vue | Arc Design Select
<a-select :options="Week.items" />
Vuetify Select
<v-select :items="Week.items" item-title="label" />
Angular Material Select
<mat-option *ngFor="let item of Week.items" [value]="item.value">{{ item.label }}
NG-ZORRO Select
<nz-option *ngFor="let item of Week.items" [nzValue]="item.value">{{ item.label }}
- 转换成 Menu 组件
toMenu
方法可以为 Ant Design Menu
、Dropdown
等组件生成数据源,格式为:{ key: number|string, label: string } []
import { Menu } from 'antd';
<Menu items={Week.toMenu()} />;
- 表格列筛选
toFilter
方法可以生成一个对象数组,为表格绑定列筛选
功能,列头中显示一个下拉筛选框,用来过滤表格数据。对象结构遵循 Ant Design 的数据规范,格式为:{ text: string, value: number|string } []
import { Table } from 'antd';
const columns = [
{
title: 'week',
dataIndex: 'week',
filters: Week.toFilter(),
},
];
// 在表头中显示下拉筛选项
<Table columns={columns} />;
- 转换成 Ant Design Pro 组件
toValueMap
方法可以为 Ant Design Pro 的ProFormFields
、ProTable
等组件生成数据源,这是一个类似 Map 的数据结构,格式为:{ [key: number|string]: { text: string } }
import { ProTable } from '@ant-design/pro-components';
<ProFormSelect valueEnum={Week.toValueMap()} />;
- 两个枚举合并(或者扩展某个枚举)
const myWeek = Enum({
...Week.raw(),
Friday: { value: 5, label: '星期五' },
Saturday: { value: 6, label: '星期六' },
});
- 使用枚举值序列来缩小
number
类型,仅 TypeScript 可用
使用 valueType
类型约束,可以将数据类型从宽泛的number
或string
类型缩小为有限的枚举值序列,这不但能减少错误赋值的可能性,还能提高代码的可读性
const weekValue: number = 8; // 👎 任意数字都可以赋值给周枚举,即使错误的
const weekName: string = 'Birthday'; // 👎 任意字符串都可以赋值给周枚举,即使错误的
const goodWeekValue: typeof Week.valueType = 1; // ✅ 类型正确,1 是一个有效的周枚举值
const goodWeekName: typeof Week.keyType = 'Monday'; // ✅ 类型正确,'Monday' 是一个有效的周枚举名
const badWeekValue: typeof Week.valueType = 8; // ❌ 类型错误,8 不是一个有效的周枚举值
const badWeekName: typeof Week.keyType = 'Birthday'; // ❌ 类型错误,'Birthday' 不是一个有效的周枚举名
type FooProps = {
value?: typeof Week.valueType; // 👍 组件属性类型约束,可以防止错误赋值,还能智能提示有效值有哪些
names?: (typeof Week.keyType)[]; // 👍 组件属性类型约束,可以防止错误赋值,还能智能提示有效值有哪些
};
本地化
enum-plus
本身不提供国际化功能,但支持通过设置 localize
可选自定义方法来实现本地化文本。你可以在项目内声明一个本地化方法,把输入的枚举label
转换成对应的本地化文本。你需要自己维护语言,并且在 localize
方法中针对当前语言返回对应的文本。如果可能的话,强烈建议你使用一个流行的国际化库,比如 i18next
下面是一个简单的示例。请注意,第一种方式其实并不是很好,因为它不够灵活,仅用于演示基本功能,请考虑使用第二种及后面的示例
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 'week.sunday':
return '星期日';
case 'week.monday':
return '星期一';
default:
return content;
}
} else {
switch (content) {
case 'enum-plus.options.all':
return 'All';
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) => <Localize value={content} />;
const Week = Enum(
{
Sunday: { value: 0, label: 'week.sunday' },
Monday: { value: 1, label: 'week.monday' },
} as const,
{
localize: sillyLocalize,
// localize: i18nLocalize, // 👍 推荐使用i18类库
// localize: componentLocalize, // 👍 推荐使用组件形式
}
);
setLang('zh-CN');
Week.label(1); // 星期一
setLang('en-US');
Week.label(1); // Monday
每个枚举类型都这样设置可能比较繁琐,你也可以通过 Enum.localize
方法来全局设置。如果静态设置和初始化选项两者同时存在,则枚举类型的初始化选项方式优先级更高。
Enum.localize = sillyLocalize;
全局扩展
Enum
已经提供了一些常用的方法,但如果这些方法还不能满足你的需求,你可以通过 Enum.extend
方法来添加自定义扩展函数。这些扩展方法将会被添加到所有的枚举类型上,即便是在扩展之前已经创建的枚举类型,也会立即生效
Enum.extend({
toMySelect(this: ReturnType<typeof Enum>) {
return this.items.map((item) => ({ value: item.value, title: item.label }));
},
reversedItems(this: ReturnType<typeof Enum>) {
return this.items.reverse();
},
});
Week.toMySelect(); // [{ value: 0, title: '星期日' }, { value: 1, title: '星期一' }]
意犹未尽,还期待更多?不妨移步 enum-plus 官网,你可以发现更多的高级使用技巧。相信我,一定会让你爱不释手!
评论