vue优化之如何管理系统变量

456 阅读7分钟

背景

最近在开发一个信息展示模块的需求,有个个人信息页面模块,页面大概要展示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,比如忘记改动另外一个地方。

变量仓库

基于以上种种,我提出来一个变量仓库的概念,实际上就是变量仓库管理系统中的各种变量。不要被名字唬住了,实际上就是封装的一种。理解起来很轻松,但是能给系统简化很多代码,而且方便后续维护。

基础类

Variable 类

好了,步入正题,实际上我们系统中后端展示的变量,一般都是对应一个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,这个就是数据对象,我们可以从这个数据对象中取出对应的值。

EnumVariable 类

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]
  }
}

系统变量类

接下来,我们定义一些系统变量类, 这些类继承于基础变量类。

AgeVariable 类

我们系统中的大部分使用多次的变量都应该遵循这样定义的原则,将后端返回的key, label进行绑定。

export class AgeVariable extends Variable {
  constructor(model = {}) {
    super(model, 'age', '年龄')
  }
}

SexVariable 类

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个字段信息

接下来我们定义好系统80个变量及他们的渲染规则,这些变量有些是时间戳,我们要对其进行转化,所以我们定义时间变量基础类.

DateTimeVariable 类

这里调用的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列列表字段

开始实现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方法就可以了。

实际上到这里,就大功告成了、

扩展1:自定义展示变量渲染

细心的同学应该会发现,我们的renderFn是传入了一个 h 函数的。有了这个 h 函数,我们可以渲染一些组件。从而来实现一些字段的自定义展示。比如枚举值高亮。

在我们需求中,有些枚举值是需要高亮的。我们系统中有个枚举类,所以我扩充了这个枚举类,可以传入高亮的值,然后再扩充我们的EnumVariable类,实现高亮类型的展示。

这里涉及代码比较多,就不贴上来了。大概实现就是这样,思路仅供大家参考,主要是思想学会就可以了。

扩展2:生成搜索栏的类JSON

因为我们筛选栏也是走 类JSON路线的,所以可以按照表格列这样的思路,扩展Variable类,来生成筛选栏的类JSON数据。

完结

又水完一篇文章。Over。希望对大家有所帮助~