一、封装前的枚举字典定义
在日常开发中,我们经常会定义字典来做些下拉选择框:
- 定义枚举
- 定义字典
代码如下:
enum ArithmeticEnum {
AES = 1,
RSA = 2,
NO = 3,
}
const ArithmeticEnumMap = {
[ArithmeticEnum.AES]: 'AES算法',
[ArithmeticEnum.RSA]: 'RSA算法',
[ArithmeticEnum.NO]: '明文',
}
console.log(ArithmeticEnumMap[ArithmeticEnum.AES])
for (const key in ArithmeticEnumMap) {
console.log(key, ArithmeticEnumMap[key])
}
然后将 ArithmeticEnumMap
传入到下拉组件中去循环遍历出选项。
<select>
<option v-for="(value, key) in ArithmeticEnumMap" :key="key" :value="key">
{{ value }}
</option>
</select>
上述的操作比较正常,但写起来不够优雅,我们接下来按枚举封装和装饰器的思路来完成这个事情。
二、定义枚举基类
首先,枚举一般都有固定格式的数据属性,例如 key
(枚举值),label
(枚举名称) 等。并且会内置很多的方法,比如 get(key: number)
、getLabel(key: number)
、toArray()
等。
那么我们首先来定义这个枚举基类:
/**
* # 类包装
* @author Hamm.cn
*/
type ClassConstructor<T = any> = {
// eslint-disable-next-line no-unused-vars
new(...args: any[]): T;
}
/**
* # 枚举基类
* @author Hamm.cn
*/
export class AirEnum {
/**
* ## 枚举的值
*/
key!: number
/**
* ## 枚举的描述
*/
label!: string
/**
* ## 实例化创建一个枚举项目
* @param key 枚举值
* @param label 枚举描述
*/
constructor(key: number, label: string) {
this.key = key
this.label = label
}
/**
* ## 获取枚举的 `Label`
* @param key `Key`
* @param defaultLabel `可选` 默认 `-`
*/
static getLabel(key: number, defaultLabel = '-'): string {
return this.get(key)?.label || defaultLabel
}
/**
* ## 查找一个枚举选项
* @param key `Key`
*/
static get<E extends AirEnum>(this: ClassConstructor<E>, key: number): E | null {
return (this as any).toArray()
.find((item: E) => item.key === key) || null
}
/**
* ## 将枚举转为数组
* @returns 枚举数组
*/
// eslint-disable-next-line no-unused-vars
static toArray<E extends AirEnum>(this: ClassConstructor<E>): E[] {
return Object.values(this)
.filter((item) => item instanceof this)
}
/**
* ## 判断 `Key` 是否相等
* @param key `Key`
*/
equalsKey(key: number): boolean {
return this.key === key
}
/**
* ## 判断 `Key` 是否不相等
* @param key `Key`
*/
notEqualsKey(key: number): boolean {
return this.key !== key
}
}
在上述基类中,我们定义了之前所说的一些好常用方法以及内置的属性,接下来我们只需要在枚举类中继承这个基类,然后添加对应的静态属性即可。
三、定义枚举字典
/**
* # 应用加密算法
* @author Hamm.cn
*/
export class ArithmeticEnum extends AirEnum {
static readonly AES = new ArithmeticEnum(1, 'AES算法')
static readonly RSA = new ArithmeticEnum(2, 'RSA算法')
static readonly NO = new ArithmeticEnum(3, '明文')
}
// 如何使用
console.log(ArithmeticEnum.AES.key)
console.log(ArithmeticEnum.AES.label)
console.log(ArithmeticEnum.getLabel(2))
console.log(ArithmeticEnum.toArray())
四、枚举封装和装饰器的碰撞
我们可以配合装饰器,来完成一些枚举类到使用组件的绑定:
class User{
nickname!: string
/**
* ## 用户密码加密方式
*/
@Dict(ArithmeticEnum)
@Form({
dict: ArithmeticEnum
})
@Table()
@Search()
arithmetic!: ArithmeticEnum
}
接下来,只要使用到用户密码加密方式的地方,都可以直接从 User
类中获取到对应的枚举字典,而无需再手动维护。
上述装饰器和组件的封装本文就不过多演示了,可以参考我们的
AirPower4T
开源项目中关于这部分的实现:AirPower On Github
五、扩展枚举
5.1 扩展属性
实际使用过程中,我们可能经常会在原有 key
,label
属性之外定义更多业务需要的字典数据,例如 color
,我们可以直接在枚举中进行扩展:
/**
* # 应用加密算法
* @author Hamm.cn
*/
export class ArithmeticEnum extends AirEnum {
/**
* ## 加密算法的颜色
* 这是个自定义的属性。
*/
color: string
static readonly AES = new ArithmeticEnum(1, 'AES算法','red')
static readonly RSA = new ArithmeticEnum(2, 'RSA算法','black')
static readonly NO = new ArithmeticEnum(3, '明文','green')
constructor(key: number, label: string, color: string) {
super(key, label)
this.color = color
}
/**
* ## 获取枚举的 `Color`
* @param key `Key`
* @param defaultLabel `可选` 默认 ``
*/
static getColor(key: number, defaultColor = ''): string {
return this.get(key)?.color || defaultColor
}
}
// 如何使用
console.log(ArithmeticEnum.getColor(2))
5.2 扩展枚举值类型
我们无法保证所有枚举的 key
都是数字,于是我们可以定义一个类型并配合泛型来支持这种场景:
type AirEnumKey = string | number | boolean
export class AirEnum<T extends AirEnumKey = number> {
key: T
// 省略更多代码...
}
接下来,无论定义什么类型的枚举,我们的封装类都完成了优雅的支持。
六、完整代码示例
/**
* # 枚举基类
* @author Hamm.cn
*/
export class AirEnum<K extends AirEnumKey = number> implements IDictionary {
/**
* ## 枚举的值
*/
key!: K
/**
* ## 枚举的描述
*/
label!: string
/**
* ## 标准 `AirColor` 颜色或自定义颜色
* 支持 `AirColor` `标准色` `十六进制` `HTML标准色`
*/
color?: AirColorString
/**
* ## 是否被禁用
* 如禁用, 下拉选项中将显示但无法选中
*/
disabled?: boolean
/**
* ## 实例化创建一个枚举项目
* @param key 枚举值
* @param label 枚举描述
* @param color `可选` 枚举扩展的颜色
* @param disable `可选` 是否禁用
*/
constructor(key: K, label: string, color?: AirColorString, disable?: boolean) {
this.key = key
this.label = label
this.color = color
this.disabled = disable
}
/**
* ## 获取枚举的 `Label`
* @param key `Key`
* @param defaultLabel `可选` 默认的标签
*/
static getLabel(key: AirEnumKey, defaultLabel = AirConstant.HYPHEN): string {
return this.get(key)?.label || defaultLabel
}
/**
* ## 获取枚举的颜色
* @param key `Key`
* @param defaultColor `可选` 默认颜色
*/
static getColor(key: AirEnumKey, defaultColor: AirColorString = AirColor.NORMAL): AirColorString {
return this.get(key)?.color || defaultColor
}
/**
* ## 获取枚举是否禁用
* @param key `Key`
*/
static isDisabled(key: AirEnumKey): boolean | undefined {
return this.get(key)?.disabled
}
/**
* ## 查找一个枚举选项
* @param key `Key`
*/
static get<E extends AirEnum<AirEnumKey>>(this: ClassConstructor<E>, key: AirEnumKey): E | null {
return (this as AirAny).toArray()
.find((item: E) => item.key === key) || null
}
/**
* ## 将枚举转为数组
* @returns 枚举数组
*/
// eslint-disable-next-line no-unused-vars
static toArray<K extends AirEnumKey, E extends AirEnum<K>>(this: ClassConstructor<E>): E[] {
return Object.values(this)
.filter((item) => item instanceof this)
}
/**
* ## 将枚举转为字典
* @returns 枚举字典
*/
// eslint-disable-next-line no-unused-vars
static toDictionary<D extends IDictionary>(this: ClassConstructor<D>): AirDictionaryArray<D> {
return AirDictionaryArray.createCustom<D>(Object.values(this)
.filter((item) => item instanceof this))
}
/**
* ## 判断 `Key` 是否相等
* @param key `Key`
*/
equalsKey(key: K): boolean {
return this.key === key
}
/**
* ## 判断 `Key` 是否不相等
* @param key `Key`
*/
notEqualsKey(key: K): boolean {
return this.key !== key
}
}
/**
* # 开放应用加密方式
* @author Hamm.cn
*/
export class OpenAppArithmeticEnum extends AirEnum {
static readonly AES = new OpenAppArithmeticEnum(1, 'AES', AirColor.WARNING)
static readonly RSA = new OpenAppArithmeticEnum(2, 'RSA', AirColor.SUCCESS)
static readonly NO = new OpenAppArithmeticEnum(3, '明文', AirColor.NORMAL)
}
七、总结
因为群里有小伙伴讨论到了这个问题,于是趁着上午没什么事情就水了这篇文章,很多的实现在 AirPower4T
项目中都有示例代码,有兴趣的朋友可以参考我们的开源项目,也欢迎各位大大的 Star
✨✨✨
Github: github.com/HammCn/AirP…
也欢迎阅读我们的专栏:“用TypeScript写前端”
That's all,各位周一愉快~