这段时间项目迭代比较快,所以很多时候对于记录的状态和类型判断我都是使用的 魔术字符串 的形式,但是这样就与代码形成了 “强耦合”, 不利于后期的维护。
例如这样的代码结构:
<!-- 用vue template来举例 -->
<template>
<table>
<!-- 其它结构 -->
<td>
<!-- 直接使用状态值判断 -->
<a @click='xxx' v-if='record.status === 0'>操作A</a>
<!-- 使用数组下标判断 -->
<a @click='xxx' v-eles-if='record.status === status[1].key'>操作B</a>
<a @click='xxx' v-else'>其他操作</a>
</td>
<!-- 其它结构 -->
</table>
</template>
特别是最近一整个分类的类型和状态的都变更了,所以在业务逻辑内的魔术字符串也需要一个一个文件去同步修改,大部分的都被替换了,但还有一些零碎的地方没有被修改到,导致不断有 BUG 被提上来,这就很头疼了。
所以我就想者怎么把业务逻辑内的魔术字符串使用一种方式替换掉,最开始我想法是把状态集中起来进行管理
阶段一:使用数组来管理 方式 ①
在数组中枚举所有状态,然后调用数组下标的方式去使用。
例如这个示例:
// order.js
const statusLabels = ['已创建', '申请中', '通过审核', '....']
就可以这样使用 statusLabels[index]
来转换 key
值为 label
,但是这样很明显会有一些问题,比如说:
这个
key
值不能是负数,虽然可以通过下面的方式来处理,但是有点蠢….
如果最小的 key 值 为-2
时,把对应的 状态名 放到数组首位,通过这样转换status[index + 2]
;依旧强耦合,在组件的条件判断中还是会使用 key 值,例如:
<a @click='xxx' v-if='record.status === 0'>编辑</a>
,
如果将来 增加/删除 状态的时候会还是需要一个一个文件修改。只能处理
key - label
的转换,并不能增加其他属性
当然如果单纯只是转换 key
和 label
可以这样使用,这个是项目一开始所使用的方式,后来陆续修改成了 方式 ② 的形式。
阶段二:数组管理 方式 ②
这个阶段因为很多组件复用了订单状态表,并且增加了很多属性,比如说图标等。
// order.js
const status = [
{
label:'已创建',
key:0,
icon: 'exclamation-circle',
},
{
label:'申请中',
key:1,
icon: 'clock-circle',
},
{
label:'通过审核',
key:2,
icon: 'check-circle',
},
// .....
]
转换
key
值为label
就可以这样使用status.find(r => r.key===record.status)
,然后通过.属性名
的方式来使用需要的属性,但是这样也会有一些问题,比如说:
- 条件判断中语义不明,会出现这样的代码,
<a @click='xxx' v-if='record.status === status[1].key'>编辑</a>
,除了我之外其他人并不知道status[1]
是什么意思;- 如果将来 增加/删除 状态的时候会还是需要去同步修改使用下标的组件,不然可能下标错位,当然可以直接追加在最后,但是我有代码洁癖的所以就没办法了。
这个阶段就是我写这篇笔记时的管理,因为项目的 v1.2.x 版本 状态码整体调整了一次,后续状态码一直有小范围的改动,所以出现了需要大面积替换魔术字符串的情况。
那么我就在考虑如何在状态管理的文件中 枚举一次 所有状态,来处理转换状态标签和操作的判断条件,如果后续如果状态码有改动也方便维护的处理方式,就有之后两个阶段的处理方式
阶段三:使用对象来管理
这个阶段有考虑过使用 Map 数据结构 因为可以遍历,但是在取值的时候会比较麻烦,并不能直接使用 链式(变量属性)来使用。
所以使用对象来管理,并且使用 Object.values
来返回所有状态数组,例如以下示例:
// order.js
const status = {
created:{
label:'已创建',
key:0
},
pending:{
label:'申请中',
key:1
},
approved:{
label:'通过审核',
key:12
},
// .....
}
const statusList = Object.values(status)
// 转换
const getOrderStatus = function(key){
const f = statusList.find(item => item.key === key)
return f || status.created
}
这个时候可以通过 getOrderStatus
函数来处理 key-label
的转换,并且可以使用 链式(变量属性)的方式去处理判断条件。
<a @click='xxx' v-if='record.status === status.created.key'>编辑</a>
那这样就可以很方便的来 转换状态 和 在判断条件中使用,并且不用担心语义化的问题。
但是这边又出现了一个问题,比如说:
在用户下的客户类别中的付费用户,如果单纯使用一个
user.js
来管理,并且使用user.customer.member.key
去判断是否展示操作内容,但是这样的话,在管理端的用户列表中,去替换用户类型的key
为label
就比较麻烦,因为层级会比较深,Object.values
只会返回一层,如果一个一个拿出来手动放到一个数组里边有会显得很呆。
所以!
阶段四:对集中管理文件中的对象拆分
因为如果同一个列别中有多级分类的话,就类似上边的例子,user.customer.member
这种用户类别。所以这个时候就把 user.js
拆分成多个文件,然后再 import
进来,这样既可以在状态判断和label转换的时候引入需要的文件就可以了。
示例:
// customer.js
const customer = {
normal:{ /*...*/ },
member:{ /*...*/ },
// ...
}
const customerTypes = Object.values(customer)
const getCustomerType = function (key) {
const record = customerTypes.find(record => record.key === key)
return record || { label: '未指定', key: 0 }
}
export { customer, customerTypes, getCustomerType }
// agent.js
const agent = {
province:{ /*...*/ },
area:{ /*...*/ },
// ...
}
const agentTypes = Object.values(agent)
const getAgentType = function (key) {
const record = agentTypes.find(record => record.key === key)
return record || { label: '未指定', key: 0 }
}
export { agent, agentTypes, getAgentType }
// user.js
import { customer, customerTypes } from "./customer.js"
import { agent, agentTypes } from "./agent.js"
const user = {
customer:customer,
agent:agent,
// 一些单独的其它类型
xxxx:{ /*...*/ },
xxxx:{ /*...*/ }
// ...
}
const allUserTypeList = [
...customerTypes,
...agentTypes,
users.xxxx,
users.xxxx,
]
// 获取账户角色类型
const getAccountType = function (type, key = 'key') {
const finder = allUserTypeList.find(item => item[key] === type)
return finder || { label: '未指定', key: 0 },
}
确定是用户类型的时候,只需要 import
对应的 角色.js 的就可以,如果不确定类型的时候就可以使用 user.js
来使用 user.xxx.xxx
来使用对应的角色类型数据,或者用 getAccountType
来获取类型。
现在我是用这种方式来管理状态的,但是我觉得还是有一些问题,准备看一下前人是否已经总结出来这种模式,所以准备把之前买来的设计模式大概翻看一下,看看有没有可用的,也算把自己的基础能力完善起来。
尾声
我在 Segmentfault 上的问题帖:项目中前端部分关于订单状态管理的一些疑问,如果有更好的想法,可以直接评论,或者 📧 Mail给我