乐趣区

关于前端:手摸手教程如何实现一个可编程的Vue动态表单

来自产品 MM 的需要

  1. 首先看一个简略的表单需要,上面是一个简略数据收集表的一部分,抉择“立刻失效”后呈现
    “ 失效日期 ” 且必填。

用 Vue + Element UI 很容易实现这个需要,开动 VSCode 一顿惯例操作,10 分钟出工。

  1. 第二天产品 MM 又找过去了,需要有一丢丢改变,如下图示:

流动类型为冲单或回馈时,反对失效条件;条件反对流动人数、流动天数 (二者为且的关系)

肿么办?启动 VSCode,又是一通惯例操作,这次改变麻烦一点,一个小时出工,当然要公布上线的话,还免不了推送、构建、测试、打包、重新部署。

  1. 然鹅,游戏并没有 over,过两天产品 MM 又来找你了,因为需要疏漏了一个中央:失效条件须要加上“峰值”判断,如下图所示:

如此周而复始,需要永远在不停迭代,前端开发疲于奔命,终于有一天你变成了这般模样:

解决之道

针对上述这种十分多变的表单需要,简略剖析一下,变动的次要是表单控件和逻辑判断,所以首先想到的就是开发一个配置式的表单,设计一个全新的表单 schema 标准,而后依据 schema 编写表单 JSON 对象,最初由表单 JSON 动静生成表单。上面就是一段常见的基于 schema 的表单 JSON 代码:

{
      title: '流动类型',
      key: 'act_type',
      type: 'radio',
      props: {options: { 1: '拉新', 2: '冲单', 3: '回馈'}
      }
},

这种思路曾经十分成熟了,有十分多成熟的开源表单我的项目采纳了这种思路,但这个计划有两个比拟显著的毛病:

1. 使用者须要学习表单 schema 标准;

2. 难于实现简单的表单交互逻辑。

为了解决第一个 schema 标准学习老本的问题,能够基于表单 schema 开发一个拖拽式的所见即所得的在线表单设计器,这个也有十分多的开源我的项目实现了,各种 form generator、form creator 等等,鄙人不才也搞了一个 VForm,有趣味的童鞋能够尝尝鲜:

VForm,一个 Vue 动静表单设计器,==>> 点此体验

为了解决第二个问题,如何实现动静表单的简单交互逻辑,也是本文的次要指标,本文的解决思路是—— 为动静表单减少可编程接口,即通过组件的交互事件和 API 办法实现交互逻辑 ,JS 代码才是王道。

实现表单的可编程接口

从第一局部的表单需要来剖析,要实现表单交互逻辑,第一步是裸露组件的交互事件,比方“流动类型”组件点击扭转后触发的 onChange 事件;第二步就是在事件中对组件进行准确操控,比方显示或暗藏某些组件、设置组件必填属性、设置组件禁用状态、增加或移除组件 CSS 款式等等。
第一步非常简单,只有给表单 schema 减少组件的自定义事件属性即可,上面 schema 给 input 组件减少了 7 个自定义事件:

  {
    type: 'input',
    icon: 'text-field',
    formItemFlag: true,
    options: {
      name: '',  // 组件根本属性
      label: '',
      labelAlign: '',
      type: 'text',
      defaultValue: '',
      placeholder: '',
      //-------------------
      onCreated: '',  // 自定义事件
      onMounted: '',
      onInput: '',
      onChange: '',
      onFocus: '',
      onBlur: '',
      onValidate: '',
    },
  },

接下来须要退出一个反对语法高亮、代码提醒的代码编辑器组件,这里抉择成熟、久经考验的 AceEditor,GitHub 有一个打包好的 ace-builds,装置应用非常简略:

装置 ace:npm i ace-builds

而后基于 ace 封装一个简略的 JS 代码编辑器,截取局部代码如下所示:

<template>
  <div class="ace-container">
    <!-- 官网文档中应用 id,这里禁止应用,在前期打包后容易呈现问题,应用 ref 或者 DOM 就行 -->
    <div class="ace-editor" ref="ace"></div>
  </div>
</template>

<script>
  import ace from 'ace-builds'
  /* 启用此行后 webpack 打包回生成很多动静加载的 js 文件,不便于部署,故禁用!!特地提醒:禁用此行后,须要调用 ace.config.set('basePath', 'path...') 指定动静 js 加载 URL!!*/
  //import 'ace-builds/webpack-resolver'

  import 'ace-builds/src-min-noconflict/theme-sqlserver' // 新设主题
  import 'ace-builds/src-min-noconflict/mode-javascript' // 默认设置的语言模式
  import 'ace-builds/src-min-noconflict/mode-json' //
  import 'ace-builds/src-min-noconflict/mode-css' //
  import 'ace-builds/src-min-noconflict/ext-language_tools'
  import {ACE_BASE_PATH} from "@/utils/config";

  export default {
    name: 'CodeEditor',
    props: {
      value: {
        type: String,
        required: true
      },
      readonly: {
        type: Boolean,
        default: false
      },
      mode: {
        type: String,
        default: 'javascript'
      },
      userWorker: {  // 是否开启语法查看,默认开启
        type: Boolean,
        default: true
      },

    },
    mounted() {ace.config.set('basePath', ACE_BASE_PATH)
    },
    // 省略 methods 办法...
  }

给 CodeEditor 减少代码提醒:

      addAutoCompletion(ace) {
        let acData = [{meta: 'VForm API', caption: 'getWidgetRef', value: 'getWidgetRef()', score: 1},
          {meta: 'VForm API', caption: 'getFormRef', value: 'getFormRef()', score: 1},
          //TODO: 待补充!!]
        let langTools = ace.require('ace/ext/language_tools')
        langTools.addCompleter({getCompletions: function(editor, session, pos, prefix, callback) {if (prefix.length === 0) {return callback(null, []);
            }else {return callback(null, acData);
            }
          }
        })
      }

封装后的 CodeEditor 成果如下(此处应用 VForm 演示):

交互事件有了,接下来实现组件的操控 API 办法,这里又分两步走:

1. 获取到组件 ref;
2. 调用组件的 methods 属性中的办法;

首先给表单减少一个 refList 的 provider 属性,在组件中 inject 注入,当每个组件创立时将本组件实例注入 refList 对象:

    //... 此处省略
    inject: ['refList'],
    //... 此处省略
    created() {this.registerToRefList()
    },
    methods: {registerToRefList() {this.refList[this.field.options.name] = this
      },
      //... 此处省略
    }

接下来,再封装一个简略的 getWidgetRef 办法,该办法通过组件名称获取到组件实例:

    getWidgetRef(widgetName, showError) {let foundRef = this.refList[widgetName]
      if (!foundRef && !!showError) {this.$message.error('Ref not found')
      }
      return foundRef
    },

通过组件实例即可调用 methods 属性中的组件办法,组件办法能够任意裁减。
实现一个简略的点击事件交互:

/* 下述代码在“喜爱喝酒还是饮料?”单选按钮 onChange 事件中执行 */
var alcoholChkWidget = this.getWidgetRef('alcoholChk')
var drinkChkWidget = this.getWidgetRef('drinkChk')

if (value === 1) {alcoholChkWidget.setHidden(false)  //setHidden 是自定义的 API 办法,管制组件显示或暗藏
  drinkChkWidget.setHidden(true)
} else {alcoholChkWidget.setHidden(true)
  drinkChkWidget.setHidden(false)
}

交互成果如下:

须要揭示的是,交互事件中的 js 代码是在表单运行期间执行,代码不会被 Babel 编译,具体执行过程是:通过 Function 生成一个匿名函数,传入指定参数后执行,没有应用低效且不平安的 eval 办法。

如果要调试交互 JS 代码也非常简单,只须要在代码中须要下断点的地位退出 ”debugger”,代码执行到此处会进入调试状态(Chrome 内核的浏览器都反对)。

论断

综上所述,实现一个可编程的 Vue 动静表单,大体实现思路如下:

1. 设计一个表单 schema 标准,基于该 schema 标准开发一个拖拽式的表单设计器;

2. 裸露组件的交互事件,并按需要实现组件的 API 办法;

3. 提供一个语法高亮、有代码提醒性能的 JS 代码编辑器;

4. 通过在交互事件中调用组件 API 办法,即可实现表单简单交互逻辑;

5. 完满实现预约指标。

鄙人的新轮子 VForm 齐全实现了上述性能,有趣味的小伙伴能够体验尝试。
点此立刻体验

具体应用文档:VForm 专栏

退出移动版