拒绝CV,高效开发后台系统

背景

这几年做了一些后台系统,最多的场景就是内容管理页面,页中主要包括表单查询及各种管理操作。随着业务扩展,开发了不少类似的页面后,发现在相同项目中各个表单之间结构、风格、样式大致相同,每次新增页面都是采用 copy大法 再修改参数逻辑。这种写法容易产生大量的重复代码,于是想到了对组件进行封装,希望提高代码复用率提升开发效率

之前做的项目主要基于Vue2+ElementUI开发的,因此本文就在这个基础上发表一些对表单封装的设计与思考。

代码已上传至GitHub仓库(Form组件),欢迎star⭐⭐⭐

设计 & 实现

配置化表单

我们先来看下面两个表单案例,表单1:


  
    
  
  
    
      
      
    
  
  
    查询
  
  
    修改
  
  
    删除
  

表单2:


  
    
  
  
    
  
  
    查询
  
  
    修改
  
  
    删除
  

我们把表单分成两个部分,输入框项按钮组。对比这两个表单来看,按钮组都是由三个按钮组成并且文本、type 都是相同的,不同的是表单1里的部分按钮带有icon。而对于输入框项也是同样,标签本质大多是相同的,不同的是一些配置,包括labelplaceholderclearable......

我们尝试把这些配置抽取出来,生成一个 JSON 格式的数据,看看会发生什么?

先来看按钮组,一个按钮需要什么?文本、绑定方法、尺寸、样式...。根据elementUI按钮组件提供的props,可以把一个按钮描述成下面这样一个配置:

{ name: 'query', type: 'primary', text: '查询', icon: 'el-icon-search', size: 'small' }

由于按钮绑定的函数里可能涉及表单外的数据,因此决定为按钮配置一个唯一标识:name,我们可以让按钮点击时触发自定义事件并传出 name。

再来看看输入框项,一个输入框一般包括哪些属性?

  • type 类型。比如 input、select、cascader(联级选择器)
  • label 输入项的描述。
  • name 字段名。
  • value 输入项绑定的值。
  • placeholder、size、clearable、disabled等其他配置。

在以上的配置基础上,我们用一个 JSON 数据描述一个表单:

{
  inputs: [
    { type: 'input', name: 'name', label: '姓名', value: '' },
    {
      type: 'select', name: 'sex', label: '性别',
      options: [{ label: '男', value: '1' }, { label: '女', value: '2' }],
      value: ''
    }
  ],
    button: [
    {
      name: 'query',
      type: 'primary',
      text: '查询',
      icon: 'el-icon-search'
    },
    {
      name: 'delete',
      type: 'danger',
      text: '删除',
      icon: 'el-icon-delete'
    }
  ]
}

相比于前面用 template 编写的表单,这种 JSON 配置就显得更加清晰且好维护。我们还可以进一步优化,对于一些大量重复的表单项配置可以提前写好,再通过模块暴露出去,比如查询、删除、修改按钮。还有对于像size这类可能需要整个表单统一的属性,可以通过依赖注入的方式全局配置。

有用的渲染函数

在有了配置后,下一步就是转换成真实的表单组件。

对于按钮组部分来说,直接 v-for 一把梭就好了。同时给按钮绑定点击事件,触发自定义事件并传递出按钮的标识符:name


  {{ button.text }}

对于输入框项的部分,会有一些不同的情况,比如el-inputel-select等等... 我最开始的写法也很简单,直接用 v-if 区分:

这种写法虽然简单粗暴,但是注意观察 会产生发现产生了大量的冗余代码,除了一些类型的输入框独有的特殊属性外,大多都是通用属性。按照这种写法,我们有多少输入框类型,这些重复的代码就要写多少次,这样是不好的。后来阅读了一些博客文章后受到启发,于是使用Vue的渲染函数进行改造。

这里就不多介绍渲染函数的相关概念了,我们来看渲染函数的用法,它需要传入三个参数:

createElement(
  'div', // {String | Object | Function},一个 HTML 标签名、组件选项对象,或者...
  {}, // 一个与模板中 attribute 对应的数据对象
  [] // {String | Array},子级虚拟节点 (VNodes),由 `createElement()` 构建而成
)

参照渲染函数的语法,对比各个类型的输入框,我们发现需要动态配置的异同属性都可以写在第二个参数中。那么接下来开始改造:

const _this = this
return createElement(
  'div',
  {},
  [
    createElement('label', _this.inputItem.label),
    // 动态渲染
    createElement(`el-${_this.inputItem.type}`, {
      props: {
        value: _this.inputValue,// 这里我的想法是:把绑定值脱离配置,由封装组件独立维护
        size: _this.size,// 可以写一个计算属性,如果有注入全局尺寸则优先使用
        disabled: _this.inputItem.disabled
      },
      attrs: {
        placeholder: _this.inputItem.placeholder
      },
      on: {
        input(newValue) {
          _this.inputValue = newValue
          _this.$emit('change', _this.inputItem.name, newValue)
        }
      }
    }, [])// 这里用同样的方法,判断是否为select再处理渲染el-option
  ]
)

最后,我们再写一个外层组件包含按钮组组件和输入框项组件。具体的就不详细说,大家可以进入GitHub仓库查看。我们来看一下最终的效果:

代码总结

封装表单这件事开始于上家公司的后台项目,近期逛社区的时候刚好看到相关文章,就想着再整理记录一下。当然,这里只完成了最简单的封装,更多复杂的需求包括表单项联动、表单校验,大家可以自行进一步开发。如果你觉得本文对你有帮助,还请帮忙点个赞❤,如果你有不同的见解,欢迎评论区讨论。

当然,本文的封装属于业务组件,并不是基础组件。因此不属于基础建设的范畴,甚至可能对于你的业务场景完全不符合。所以,在开发中没有绝对的最优解决方案,只有是否合适你的业务场景。

你可能感兴趣的:(拒绝CV,高效开发后台系统)