项目中的类型和状态管理

这段时间项目迭代比较快,所以很多时候对于记录的状态和类型判断我都是使用的 魔术字符串 的形式,但是这样就与代码形成了 “强耦合”, 不利于后期的维护

例如这样的代码结构:

<!-- 用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 ,但是这样很明显会有一些问题,比如说:

  1. 这个 key 值不能是负数,虽然可以通过下面的方式来处理,但是有点蠢….
    如果最小的 key 值 为 -2 时,把对应的 状态名 放到数组首位,通过这样转换 status[index + 2]

  2. 依旧强耦合,在组件的条件判断中还是会使用 key 值,例如:<a @click='xxx' v-if='record.status === 0'>编辑</a>,
    如果将来 增加/删除 状态的时候会还是需要一个一个文件修改。

  3. 只能处理 key - label 的转换,并不能增加其他属性

当然如果单纯只是转换 keylabel 可以这样使用,这个是项目一开始所使用的方式,后来陆续修改成了 方式 ② 的形式。


阶段二:数组管理 方式 ②

这个阶段因为很多组件复用了订单状态表,并且增加了很多属性,比如说图标等。

// 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),然后通过 .属性名 的方式来使用需要的属性,但是这样也会有一些问题,比如说:

  1. 条件判断中语义不明,会出现这样的代码,<a @click='xxx' v-if='record.status === status[1].key'>编辑</a>,除了我之外其他人并不知道 status[1] 是什么意思;
  2. 如果将来 增加/删除 状态的时候会还是需要去同步修改使用下标的组件,不然可能下标错位,当然可以直接追加在最后,但是我有代码洁癖的所以就没办法了。

这个阶段就是我写这篇笔记时的管理,因为项目的 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 去判断是否展示操作内容,但是这样的话,在管理端的用户列表中,去替换用户类型的 keylabel 就比较麻烦,因为层级会比较深,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给我