页面状态复杂时,我总是会用策略模式来提升代码可读性,并尽量减轻心智负担,最近我按照我的思路,抽象出来一个操作类,在项目里用着不错,分享出来。
思路
先讲一下思路吧,之前写的状态大概是:
1 2 3 4 5 6 7 8
| const stateMap = { '0': '活动未开始', '1': '活动进行中', '2': '活动已结束', } const typeFormServer = 1 const showText = stateMap[typeFromServer] console.log(showText)
|
此外,还有需要展示不同的dom样式的问题
1 2 3 4 5 6 7 8 9 10 11 12
| const stateMap = { '0': { text: '活动未开始', style: 'unstart' } } const btnStyle = stateMap[0].style
|
诚然上面解决了我的问题,但是项目中很多页面都有这种状态信息展示,管理起来异常繁琐,可能某天心情好 stateMap 直接用数组,下标当key了,又可能我只关心样式,将style当key,总之我需要定义一种规则,保证代码简单可阅读。
所以我写出一个 KeyMap 类统一管理这些状态。
写法上我希望是这种:
不再以某种(后端返回的type或前端style)当key,传入数组,KeyMap 内部为每个字段都编制成key,方便按照value/sign字段检索。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const keyMap = new KeyMap([ { text: '待接单', sign: 'Pending', value: 1, }, { text: '进行中', sign: 'Processing', value: 2, }, { text: '已完成', sign: 'Completed', value: 3, } ])
keyMap.getValue({sign: 'Pending'}) keyMap.getText({value: 2}) keyMap.getSign({value: 2})
keyMap.includes(['Pending', 'Processing'], 0)
keyMap.includes(['Pending', 'Processing'], 3)
|
实现
在constructor里,初始化所有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| export class KeyMap { private map: MapType[] public sign!: any constructor(map: MapType[]) { this.map = map this.initValue() this.initSign() }
private initProperties() { this.map.forEach((v) => { this[v.value] = { text: v.text, sign: v.sign } }) } private initSign() { this.sign = this.map.reduce((acc, cur) => { return Object.assign(acc, { [cur.sign]: { text: cur.text, value: cur.value, }, }) }, {}) } }
|
下面是查询方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class KeyMap { public getText({ value, sign }: Partial<TextQuery>): string | number { if (sign) return get(this, ['sign', sign, 'text'], '') return get(this, [value ?? '', 'text'], '') } public getSign({ value }: Partial<TextQuery>): string | number { return get(this, [value ?? '', 'sign'], '') } public getValue({ sign }: Partial<TextQuery>): string | number { return get(this, ['sign', sign ?? '', 'value'], '') } public includes(signs: (string | number)[], value: MaybeRef<string | number>) { const allValue = signs.map(v => this.getValue({ sign: v })) return allValue.includes(unref(value)) } }
|
用起来自我感觉良好,分享一下我的使用经验
展示状态文本
1 2 3 4 5 6 7
| const orderType = ref(0) onMounted(async ()=> { orderType.value = await loadType() }) const orderStateText = computed(() => { return keyMap.getText({ value: unref(orderType) }) })
|
v-if 根据状态区别展示元素
1 2 3 4 5 6 7 8
| <template> <div v-if="orderType === keyMap.getValue({ sign: 'Reject' })" class="reject-card"> <span class="card-title">拒绝原因</span> <span class="value">时间与其他任务冲突,无法接单 </span> </div> </template>
|
某些按钮只能在Pending、Processing、Completed状态中展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div v-if="keyMap.includes(['Pending', 'Processing', 'Completed'], orderType)" class="page-action"> <div class="btn-group"> <template v-if="orderType === keyMap.getValue({ sign: 'Pending' })"> <button class="btn cancel" @click.stop="cancelOrder"> 取消点单 </button> </template> <template v-if="orderType === keyMap.getValue({ sign: 'Processing' })"> <button class="btn" @click.stop="ensureOrder"> 确认完成 </button> </template> <template v-if="orderType === keyMap.getValue({ sign: 'Completed' })"> <button class="btn" @click="goEvaluate"> 前往评价 </button> </template> </div> </div> </template>
|
另外,如果要展示状态的副标题或者其他信息,可以在每一条状态中多加一个rest,只需在init时将reset也保存,再添加一条getRest方法即可。
我这里为了简单,为了实现开闭原则也很简单,init时候将所有字段都存上,再添加一个createGetter方法,传好获取路径的回调函数,此时这个KeyMap便是对添加开放,无需修改了。
也就是说:
1 2 3 4 5 6 7 8 9 10 11
| private initProperties() { this.map.forEach((v) => { const { value, ...other } = v this[value] = other }) } public createGetter(getter: (keyMap: KeyMap) => any) { return value => getter(this, value) }
|
而 createGetter 使用方法
1 2 3 4 5
| const getPersonType = keyMap.createGetter((keyMap, value) => { return get(keyMap, value, 'PersonType') }) getPersonType(0)
|