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


    背景

    最近在开发一个信息展示模块的需求,有个个人信息页面模块,页面大概要展示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> 
    
    • 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
    • 31
    • 32

    最大的问题,也就是现在大家普遍开发的模式,就是字段的枚举值没有和后端返回的字段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
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这个变量类使用的话,会传入一个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]
      }
    } 
    
    • 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
    • 31
    • 32

    系统变量类

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

    AgeVariable 类

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

    export class AgeVariable extends Variable {
      constructor(model = {}) {
        super(model, 'age', '年龄')
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    SexVariable 类

    const SEX_MAP = {
      1: "男",
      2: "女",
    }
    
    export class SexVariable extends EnumVariable {
      constructor(model = {}) {
        super(model, "sex", "性别", SEX_MAP)
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用

    有了上面两个类的铺垫,使用就很简单了。

    <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> 
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    看到这里,你可能会有点疑问,这有优化吗,可能代码还变多了。(手动狗头)确实,这里看起来代码量确实变多了,但是,我们实现了年龄变量的解耦,我们不再需要去关心年龄怎么渲染的,也就是不需要关心这是一个枚举变量还是普通变量,我们只需要拿到特定的变量进行渲染。

    这一小步,却为后面的封装留了一个巨大的缺口。

    渲染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)
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    个人信息渲染组件

    有了这些定义好的变量,我们只需要使用就好了。你会发现使用起来都是这些代码。

     <li>
        <span class="label">{{ sexV.label }}:</span>
        <span class="value">{{ sexV.renderFn() }}</span>
      </li> 
    
    • 1
    • 2
    • 3
    • 4

    所以我们可以定义渲染组件,遍历循环即可。这样信息就渲染完成了。这样使用这个组件有一个好处,就是我们可以一次性处理好空值的显示,比如用户年龄没写,我们可以处理为 ‘–’。

    到这,就告一段落了、

    渲染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)
      }
    } 
    
    • 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

    枚举类不太一样,所以需要重写他的 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));
        }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    所以80列列表字段,我们只需要调用Variable类的genTableColumn方法就可以了。

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

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

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

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

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

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

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

  • 相关阅读:
    【代码随想录训练营】Day50-动态规划
    【计算机毕业设计】211校园约拍微信小程序
    Qt 创建控件的两种方式
    Draw软件安装下载
    继承(二) —— 基类和派生类对象的赋值转换
    # 如何在Windows下运行Linux程序
    上采样之反卷积操作
    本机MySQL数据库安装
    Java运算符
    2022深圳杯数学建模赛题思路 比赛通知
  • 原文地址:https://blog.csdn.net/web22050702/article/details/125510189