前端枚举的项目实践

1,870 阅读5分钟

ts版本:juejin.cn/post/723322…

定义枚举

在绝大多数项目中定义枚举都是用数组加对象的形式

const sex = [
    {
        label: '男',
        value: 0
    },
    {
        label: '女'
        value: 1
    }
]

这种方式在大部分的ui框架中可以作为选择器或其他表单控件的数据来源,有足够的扩展性。如果有个将“男”的文字颜色改为蓝色,”女“改为红色的需求,只需要再加个字段就能实现。

const sex = [
    {
        label: '男’,
        value: 0,
        color: 'bule'
    },
    {
        label: '女'
        value: 1,
        color: 'red'
    }
]

映射枚举

使用上面的方式定义枚举唯一不优雅的地方就是在映射枚举的label上,在项目中映射枚举的方式是真的多。

比如在直接在模板中直接v-if判断

<span v-if="row.sex === 0"> 男 </span>
<span v-if="row.sex === 1"> 女 </span>

使用上面这种方式在新增加一个性别时 ,不仅要在定义的枚举上加,还要在模板中在加一个v-if,这无疑是给项目增加了更多的心里负担!

比较好的是在数据的对象上增加一个新的字段,如:sexLabel ,将枚举的label赋值给 sexLabel 然后在到模板中使用。后续即使有新的属性也只需要在定义一个新的值。

<template>
    <span> {{ row.sexLabe }} </span>
</template>
<script setup>
cosnt row = { sex; 0 }
let sexLabel = ’-‘
sex.foreEace(item => {
    if (itme.value === row.sex) {
        row.sexLabel = item.label
     }
})
</script>

除了foreace还可以使用find方法,只需要一行代码就行,在长达一年的时间中就是使用find的方式映射枚举,用着用着发现重复的代码还是有点多。

row.sexLabel = sex.find(item => item.value === row.sex)?.label || ''

之后尝试封装成公用函数,虽然代码量是少了,但每次都要映入这个公共函数,还是不够优雅!

/**
 * 获取状态对应的标签
 * @param enums 状态枚举列表
 * @param val 值
 * @returns
 */
export function getLabelByValue(enums, val) {
  const data = enums.find((ele) => ele.value === val)
  if (data) {
    return data.label
  }
  return '-'
}
row.sexLabel = getLabelByValue(sex, row.sex)

我的最佳实践

思考

find方法重复的代码多,封装公共方法每次都要引入,特别部分枚举在多个页面同时使用时,不仅要引入枚举,还要引入对应的方法,增加了心里负担。

为了解决这部分问题,我想了很多方案。最好的方式是在枚举本身可以有个根据对应的值映射对应的label的方法,这是最好的方式!

row.sexLabel = sex.getLabelByValue(row.sex)

定义枚举的方式是数组,数组本身是没有这个方法,如何在这个枚举数组上增加这个方法?在数组的原型上添加肯定是不行的,那相当于所有定义的数组都会拥有该方法,这个方法应该是枚举独有的!

所以如何单独给枚举数组增加这个方法是主要的问题!回忆自己所学的知识,添加私有方法应该是面向对象的知识,js面向对象这一块的知识,只是在某站看过视频,没有深入了解过,本身工作这一年多也是使用vue开发,react也只是听过的情况下。如果是枚举是用对象还好,数组虽然也是对象,但完全无法下手,就此卡住。

而自己的技术菜,没看过几本书,每天写业务,没啥技术沉淀,相信看到这的,基本已经想到如何在枚举数组上定义私有方法了吧。

解决方案

之后在看js红宝书时,我找到了如何在枚举数组添加这个私有方法的解决办法!那就是继承后扩展

es6的class语法解决了使用构造函数很多问题,其中就包括继承!es6使用extends可以继承内置类型,就可以不影响数组本身,只在继承的新类型添加私有方法的能力!

定义一个 EnumArray 的新类型,在添加 getLabelByValue 方法,就实现了我想要的效果。

class EnumArray extends Array {
    /** 根据值获取标签 */
    getLabelByValue(val) {
        return this.find((item) => item?.value === val)?.label || '-'
    }
}

使用 new EnumArray() 来创建这个枚举,然后sex得到就是最开始定义的那个sex枚举数组了

const sex = new EnumArray(
    {
        label: '男,
        value: 0
    },
    {
        label: '女'
        value: 1
    }
)

映射枚举的方式就可以链式调用 getLabelByValue方法,传入对应值就可以获取到对应的label

row.sexLabel = sex.getLabelByValue(row.sex)

除了getLabelByValue方法,还可以添加其他方法,比如根据值获取整个对应枚举对象的方法。

getEnumObjByValue(value) {
    return this.find(item) => item?.value === val)
}

最后,直接使用new EnumArray的方式会造成一点小小的误解,所以可以写个 createEnum 函数,接收一个数组的方式创建枚举。

function createEnum(enums) {
  return Object.freeze(new EnumArray(....enums)
}              

使用方法

const sex = createEnum([
    {
        label: '男',
        value: 0
    },
    {
        label: '女'
        value: 1
    }
])

Object.freeze这个方法是冻结这个枚举数组,在当把枚举定义在vue2的data时,可以让vue不监听这个枚举的变化,因为vue会重写data中的数组方法,所以使用vue并且将枚举放置在data中必须保留Object.freeze。其他场不会重写原始类型的场景也可以省略Object.freeze 方法。

以上就是目前在项目中使用枚举的方式了,如果有更好更优雅的方式,可以分享,一起交流,一起进步

踩的坑

老项目大部分的枚举都是定义在 .vue文件上,在多个页面都是直接复制粘贴获取,导致每次一改要改好多地方,一不小心就改漏了,后面写的枚举都是放在 utils/enumeration.js文件里,哪里用了就直接引用。

枚举的命名上也踩过坑,之前枚举都是对应后端返回的字段命名,但将枚举放在一个文件中就会出现命名重复的问题,比如状态 state好多地方都是这个名字,但是内容不一样,可以加个前缀,如订单页面,就加个订单的前缀,末尾加上Enums的后缀,表示这是个枚举。

在翻看老代码时,有部分常用的枚举都是定义在vuex上,当时我啥不懂,又不想看老代码,没发现。。。

不过我建议最好不要将枚举放置在vuex这样的状态管理上,毕竟枚举时静态的,不需要管理状态吗,如果要放在vuex上,最好使用Object.freeze包裹,不然大量的枚举都会被监听,对项目的影响是很大的。