最近在开发一个信息展示模块的需求,有个个人信息页面模块,页面大概要展示80个信息字段,这些信息字段,大部分是枚举值,还有一些是时间类型的展示,比如注册时间这些,少部分是直接拿到字段就可以展示的。另外,这80个信息字段,也需要在列表中展示。
按照传统的模式进行开发,肯定就是一把梭。将所有的字段一一列举出来展示,但是这样,同个变量要在代码写好几遍。可能这样说起来比较生硬。举个例子,比如我想展示性别这个字段,性别有男和女两个枚举值。一般我们在开发的时候,会先引入枚举值的定义,然后根据枚举值判断应该显示什么值。
<template>
<div id="app">
<ul>
<li>
<span class="label">年龄:</span>
<span class="value">{{ user.age }}</span>
</li>
<li>
<span class="label">性别:</span>
<span class="value">{{ SEX_MAP[user.sex] }}</span>
</li>
</ul>
</div>
</template>
<script> const SEX_MAP = {
1: "男",
2: "女",
}
export default {
name: "app",
data() {
return {
SEX_MAP: Object.freeze(SEX_MAP),
user: {
age: 24,
sex: 1,
},
}
},
} </script>
最大的问题,也就是现在大家普遍开发的模式,就是字段的枚举值没有和后端返回的字段key进行绑定。
这种有什么问题呢?
第二个问题就是比较繁琐了,80个字段我都这么定义一遍,时间都用来加班了。像我这种“伪效率”的,是不能接受的。
另外一个问题就是这些字段,在列表还需要展示,我需要把一个个字段抠出来,放到列表中。如果放到后面测试的时候,一个字段不对,我要改动好几个地方,不符合软件设计中的唯一性。容易给系统带来Bug,比如忘记改动另外一个地方。
基于以上种种,我提出来一个变量仓库的概念,实际上就是变量仓库管理系统中的各种变量。不要被名字唬住了,实际上就是封装的一种。理解起来很轻松,但是能给系统简化很多代码,而且方便后续维护。
好了,步入正题,实际上我们系统中后端展示的变量,一般都是对应一个key值,这个key值基本上是不会改变的。比如像年龄这个变量,key就是age。基于此,我们先定义一个基础类。
class Variable {
constructor(model = {}, key, label) {
this.key = key
this.value = model[key]
this.label = label
}
renderFn(h, value = this.value, model) { // 渲染函数传入 h, 继承的时候可以渲染 jsx
return value
}
}
这个变量类使用的话,会传入一个model,这个就是数据对象,我们可以从这个数据对象中取出对应的值。
class EnumVariable extends Variable {
constructor(model = {}, key, label, _enum, optionProp) {
super(model, key, label)
this.enum = _enum
this.optionProp = optionProp || { labelKey: "label", valueKey: "value" }
if (!this.optionProp.labelKey) this.optionProp.labelKey = "label"
if (!this.optionProp.valueKey) this.optionProp.valueKey = "value"
}
renderFn(h, value = this.value, model) {
if (this.enum instanceof HighlightEnum) {
return this.enum.getHighlightVNode(h, value)
}
if (this.enum instanceof Enum) {
return this.enum.getTextByValue(value)
}
if (Array.isArray(this.enum)) {
const resultItem = this.enum.find(
(item) => item[this.optionProp.valueKey] === value
)
if (resultItem) return resultItem[this.optionProp.labelKey]
return ""
}
return this.__renderObj(value)
}
__renderObj(value = this.value) {
return this.enum[value]
}
}
接下来,我们定义一些系统变量类, 这些类继承于基础变量类。
我们系统中的大部分使用多次的变量都应该遵循这样定义的原则,将后端返回的key, label进行绑定。
export class AgeVariable extends Variable {
constructor(model = {}) {
super(model, 'age', '年龄')
}
}
const SEX_MAP = {
1: "男",
2: "女",
}
export class SexVariable extends EnumVariable {
constructor(model = {}) {
super(model, "sex", "性别", SEX_MAP)
}
}
有了上面两个类的铺垫,使用就很简单了。
<template>
<div id="app">
<ul>
<li>
<span class="label">{{ ageV.label }}:</span>
<span class="value">{{ ageV.renderFn() }}</span>
</li>
<li>
<span class="label">{{ sexV.label }}:</span>
<span class="value">{{ sexV.renderFn() }}</span>
</li>
</ul>
</div>
</template>
<script> import { SexVariable, AgeVariable } from "./Variable"
export default {
name: "app",
data() {
return {
user: {
age: 24,
sex: 1,
},
}
},
computed: {
sexV() {
return new SexVariable(this.user)
},
ageV() {
return new AgeVariable(this.user)
},
},
} </script>
看到这里,你可能会有点疑问,这有优化吗,可能代码还变多了。(手动狗头)确实,这里看起来代码量确实变多了,但是,我们实现了年龄变量的解耦,我们不再需要去关心年龄怎么渲染的,也就是不需要关心这是一个枚举变量还是普通变量,我们只需要拿到特定的变量进行渲染。
这一小步,却为后面的封装留了一个巨大的缺口。
接下来我们定义好系统80个变量及他们的渲染规则,这些变量有些是时间戳,我们要对其进行转化,所以我们定义时间变量基础类.
这里调用的timeFormate是一个时间格式化函数,这里我就省略实现了,知道大概意思就可以了。
class DateTimeVariable extends Variable {
constructor(model = {}, key, label) {
super(model, key, label)
}
renderFn() {
if (this.value === 0) return ''
return timeFormate(this.value)
}
}
有了这些定义好的变量,我们只需要使用就好了。你会发现使用起来都是这些代码。
<li>
<span class="label">{{ sexV.label }}:</span>
<span class="value">{{ sexV.renderFn() }}</span>
</li>
所以我们可以定义渲染组件,遍历循环即可。这样信息就渲染完成了。这样使用这个组件有一个好处,就是我们可以一次性处理好空值的显示,比如用户年龄没写,我们可以处理为 ‘–’。
到这,就告一段落了、
开始实现80列的列表字段,这里开始展示这样封装的意义了。我们知道,软件设计中,有个很重要的概念就是唯一性。比如我们定义常量,就是为了保证全局唯一。全局唯一能够给我们带来很多好处,比如方便后续维护。
我们以前开发的模式的话,肯定就是每一列每一列去写值的定义及枚举。这样我们很难关注到多少个地方使用了这个变量。后面改动变量值的定义的时候,也很麻烦,容易改动多个地方导致出问题。
我们项目中用了 类JSON
的方式定义 elementui
的表格列,所以每一列我只需要生成一个 类JSON
就行了。
我们可以在我们的Variable基类,定义一个 genTableColumn
的方法,这个方法专门实现生成一个表格的类JSON
列。
class Variable {
constructor(model = {}, key, label) {
this.key = key
this.value = model[key]
this.label = label
}
renderFn(h, value = this.value, model) { // 渲染函数传入 h, 继承的时候可以渲染 jsx
return value
}
genTableColumn(extraProp, openRenderMode = false) {
const column = {
prop: this.key,
label: this.label,
}
if (openRenderMode) {
const that = this
column.render = function(h) {
const { row } = h
return that.renderFn(h, row[that.key], row)
}
}
return Object.assign(column, extraProp)
}
}
枚举类不太一样,所以需要重写他的 genTableColumn 方法。
class EnumVariable extends Variable {
genTableColumn(extraProp, openRenderMode = false) {
const that = this
if (openRenderMode) {
return Object.assign({
render(h) {
const { row } = h
const result = that.renderFn(h, row[that.key], row)
if (result === null || result === undefined || result === '') return '--'
return result
}
}, super.genTableColumn(extraProp));
}
const column = {
type: 'enum',
options: this.enum instanceof Enum ? this.enum.getOptions() : this.enum
}
if (this.optionProp) column.optionProp = this.optionProp
return Object.assign(column, super.genTableColumn(extraProp));
}
}
所以80列列表字段,我们只需要调用Variable类的genTableColumn方法就可以了。
实际上到这里,就大功告成了、
细心的同学应该会发现,我们的renderFn是传入了一个 h 函数的。有了这个 h 函数,我们可以渲染一些组件。从而来实现一些字段的自定义展示。比如枚举值高亮。
在我们需求中,有些枚举值是需要高亮的。我们系统中有个枚举类,所以我扩充了这个枚举类,可以传入高亮的值,然后再扩充我们的EnumVariable类,实现高亮类型的展示。
这里涉及代码比较多,就不贴上来了。大概实现就是这样,思路仅供大家参考,主要是思想学会就可以了。
因为我们筛选栏也是走 类JSON
路线的,所以可以按照表格列这样的思路,扩展Variable类,来生成筛选栏的类JSON
数据。