在前端开发时,经常会使用到业务常量
如下:
const fieldType = [
{ value: 1, label: '文本' },
{ value: 2, label: '数字' },
{ value: 3, label: '视频' },
]
复制代码
使用它的场景大致有三类:
1. 当做表单输入的可选项被遍历
作为select、radio、checkbox等的选项值
<el-select v-model="type">
<el-option
v-for="item in fieldType"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
复制代码
这是常量最普遍被使用的方式,所以大多数常量都被存储为数组,并且包含value
、label
两个字段
2. 把value
转为label
显示
有时后端提供的数据的是value
,那么前端显示时需要把数字转化为文字。这个过程在实践中,是很重复、繁琐的:
enumsFilter(value) {
if (!value) {
return ''
}
const item = fieldType.find(item => item.value === value)
return item ? item.label : ''
}
复制代码
上述逻辑大概率会在filter
中实现(vue3已取消filter功能),也可能出现在其他函数中。
NOTE:最重要的是,这段逻辑无处不在,很恶心。
3. 用英文名获取value
,消除魔法值
if(typeValue === 1){
// do something
}
复制代码
魔法值的问题很明显:没有语义,不易于维护。
于是就出现了使用英文名字来获取value
的方式,使代码语义化:
if(typeValue === fieldTypeEnum.TEXT){
// do something
}
复制代码
那么就需要数据原来维护,英文名和value
的对应关系
fieldTypeEnum = {
TEXT: 1,
NUMBER: 2,
VIDEO: 3,
...
}
复制代码
NOTE:一个常量,需要维护两份数据。因为耗费精力,偷懒就造成魔法值泛滥。
上述的后两种使用场景,都有各自的问题。于是我尝试简化这两个场景的代码。
网上的解决方案:
对于上述常量使用场景的相应问题,网上也有一些解决方案:
数据源维护value、label、name
三个字段,并使用createEnum
处理数据源:
export const fieldType = createEnum([
{ value: 1, label: '文本', name: 'TEXT' },
{ value: 2, label: '数字', name: 'NUMBER' },
{ value: 3, label: '视频', name: 'VIDEO' }
])
复制代码
createEnum
返回值为数组,会携带两个方法getLableByValue
、getValueByName
,来覆盖上述的后两种场景:value => label
,name => value
,使用示例:
fieldType // list
fieldType.getLableByValue(1) // '文本'
fieldType.getValueByName('TEXT') // 1
复制代码
我的优化:
我的纠结点在于:有没有另一种方式,能把函数调用省略掉,一是因为写函数名也很麻烦,二是因为使用调用,代码就会有ReferenceError
报错的概率。
尝试了很多种方式,终于找到了完美的方式,覆盖三种常量的使用场景,并且使用更简洁:
export const fieldType = createEnum([
{ value: 1, label: '文本', name: 'TEXT' },
{ value: 2, label: '数字', name: 'NUMBER' },
{ value: 3, label: '视频', name: 'VIDEO' }
])
fieldType() // list
fieldType(1) // '文本'
fieldType(1, 2, 3) // '文本、数组、视频' 代码未实现
fieldType('TEXT') // 1
复制代码
createEnum
返回的是函数,通过传参来获取不同的数据,类似于枚举的效果。
优势:
- 省略了
value ==> label
过程中,恶心、重复的find
过程; - 三种场景的使用方式都是函数调用,一致且简洁;
代码实现很简单:
/**
* 业务常量的使用方式扩展
* @param {Array} source
[
{ value: 1,label: '空间站',name: 'SPACE' },
{ value: 2, label: '实践任务', name: 'PRACTICE' },
{ value: 3, label: '其他系统', name: 'OTHERS' }
]
@returns {Function} fn
fn() ===> source
fn(value) ===> label
fn(name) ===> value
*/
export default function createEnum(source) {
const sourceMap = new Map()
source.map(item => {
sourceMap.set(item.name, item.value)
sourceMap.set(item.value, item.label)
})
return function(key) {
if (key) {
return sourceMap.get(key)
} else {
return source
}
}
}
复制代码
考虑到不是所有的常量都会有第三种使用场景(name=>value),所以
name
是可选的。你可以碰到此场景时,再回来定义name,以避免多余的工作量,毕竟起英文名也很费精力。
当时想了很多种方式,把source
数组和sourceMap
结合在一起,代理proxy
、迭代器iterator
、原型prototype
等等,最后选用了函数调用的方式,简单、有效。