Vue表单验证插件的制作过程


Posted in Javascript onApril 01, 2017

前言

前段时间,老大搭好了Vue的开发环境,于是我们愉快地从JQ来到了Vue。这中间做的时候,在表单验证上做的不开心,看到vue的插件章节,感觉自己也能写一个,因此就自己开始写了一个表单验证插件va.js。 当然为什么不找个插件呢? vue-validator呀。

1.我想了下,一个是表单验证是个高度定制化的东西,这种网上找到的插件为了兼顾各个公司的需求,所以加了很多功能,这些我们不需要。事实证明,vue-validator有50kb,而我写的va.js只有8kb。
2.另一个是,vue-validator的api我真的觉得长, 动不动就v-validate:username="['required']",这么一长串,而我设计的调用大概如——v-va:Money

当然,本文仅是展示下,如何写个满足自己公司需求的vue表单验证插件。下面介绍下思路。

一、表单验证模块的构成

任何表单验证模块都是由 配置——校验——报错——取值 这几部分构成的。

  • 配置: 配置规则 和配置报错,以及优先级
  • 校验: 有在 change 事件校验, 在点击提交按钮的时候校验, 当然也有在input事件取值的
  • 报错: 报错方式一般要分,报错的文字有模板,也有自定义的
  • 取值: 将通过验证的数据返还给开发者调用

下面是我老大针对公司项目给我提出的要求

  • 集中式的管理 校验规则 和 报错模板。
  • 报错时机可选
  • 校验正确后的数据,已经打包成对象,可以直接用
  • 允许各个页面对规则进行覆盖,对报错信息进行自定义修改,以及允许ajax获取数据后,再对规则进行补充
  • 按顺序来校验,在第一个报错的框弹出错误

我就很好奇地问, 为什么要这样子呢?然后老大就跟我一条一条解答:

  • 集中式管理规则,和报错模板的好处,就是规则可以全局通用,一改全改。老大跟我说,光是昵称的正则就改了三次。如果这些正则写在各个页面,o( ̄ヘ ̄o#)哼,你就要改N个页面了
  • pc和移动的流程不一样,pc很多校验都要在change事件或者input事件就校验并报错了,而移动则一般是要到提交按钮再进行校验。所以写插件的时候要做好两手准备。然后,报错用的ui要可以支持我们现在用的layer插件。当然以后这个报错的ui也可能变,所以你懂滴。
  • 当然原来jq时代,我们的公用表单验证,就能验证完了,把数据都集合到一个对象里。这样ajax的时候,就不用再去取值了。你这个插件耶要达到这个效果
  • 原来jq的那个公用脚本,正则和报错都集中到一个地方去了,在很多地方已经很方便了。但是在一些页面需要改东西的时候还不够灵活。像RealName这个规则,最早是针对某个页面配置的,用的是后端接口上的字段名。另一个支付页,后端接口上的字段名改成了PayUser了,但是正则还是RealName的,原来我们是要复写一下RealName。这个就不太方便也不好看了。另外一个,支付金额,有最大值和最小值的限制,这个需要从后端获取的。你也要考虑这个情况。要做到各个页面上也能有一些灵活的地方可以修改规则,自定义报错等等。
  • 为什么要按顺序校验啊?你忘了上次牛哥让我们输入框,从上到下,按顺序报错。不然用户都不知道哪个地方错了。还有规则也是要按顺序的。哦哦哦。看来这次我放东西的时候,要用下数组了。尽量保持顺序。

我听了之后,大致懂了,原来之前自己写的jq表单验证还有这么多不舒服的点。-_-|||接下来,是看看vue给我的好东西。让我来写

二、Vue 的插件怎么写

我一个vue小白,怎么就开始写vue插件了呢?那是因为想解决方案的时候,翻Vue文档翻到了这里

Vue表单验证插件的制作过程

这些东东,等我写完va.js的时候,感觉尤大写的真的是很清楚了。

其实我是想写个指令来完成表单验证的事的。结果发现可能有2-3个指令,而且要再Vue.prototype上定义些方法,好让各个子实例内部也能拓展规则。于是老大说,这就相当于插件了。这让我很是吃鲸。

va.js主要用的是 Vue指令

Vue表单验证插件的制作过程

Vue表单验证插件的制作过程

Vue 文档真的写得很用心,但是我再补充一点吧

vnode.context 就是Vue的实例

我们做项目的时候,经常一个根组件上挂着N个子组件,子组件上又可能挂着N个子组件。vnode.context获取的实例,是绑定该指令的组件的实例。这个就相当好用了。你可以做很多事情

当然还用了点Vue.prototype
Vue.prototype.$method 就是可以在各个组件上调用的方法。可以在组件内部用 this.$method调用的

## 三、具体实现的思路 ##

核心思路如下图:

Vue表单验证插件的制作过程

规则的构造函数

//va配置的构造函数
function VaConfig(type, typeVal, errMsg, name, tag){
 this.type = type, this.typeVal = typeVal, this.errMsg = errMsg, this.name = name, this.tag = tag
}
  • type: nonvoid(非空), reg(正则), limit(区间), equal(与某个input相等),unique(不能相同)
  • typeVal: 根据不同type设置不同的值
  • errMsg: 自定义的报错信息
  • name: 用来传ajax的字段,如Password, Username
  • tag:用来报错的名字,如‘银行账号',‘姓名'

设置了三种规则

1.默认规则: 只要绑定指令,就默认有的校验。 比如非空的校验。 可以额外加修饰符来去除
2.选项规则: 通过Vue指令的修饰符添加的规则。
3.自定义规则: Vue指令属性值上添加的规则。
同一个type的规则只存在一个,也就是说,如果type为reg(正则),那么会互相覆盖。
覆盖的优先级: 自定义规则 > 选项规则 > 默认规则

思路讲的多了。也不知道怎么讲了,下面大家直接看源码把。

源码

/*
* 流程: 绑定指令-> 设置配置(vaConfig) -> 校验(check) -> 报错(showErr) 或 自定义报错
 */

var va = {};

function unique(arr){
 var hashTable = {}, newArr = [];
 for(var i = 0;i < arr.length;i++){
 if(!hashTable[arr[i]]){
  hashTable[arr[i]] = true;
  newArr.push(arr[i]);
 }
 }
 return newArr;
}

function addClass(dom, _class){
 var hasClass = !!dom.className.match(new RegExp('(\\s|^)' + _class + '(\\s|$)'))
 if(!hasClass){
 dom.className += ' ' + _class
 }
}

//校验函数
function check(v, conditions){
 var res = 0;     //0代表OK, 若为数组代表是某个字段的错误
 //验证函数
 var cfg = {
 //非空
 nonvoid: (v, bool)=>{
  if(bool){
  return v.trim() ? 0 : ['nonvoid'];
  }else{
  return 0;
  }
 },
 reg:(v, reg)=> reg.test(v) ? 0 : ['reg'],  //正则
 limit:(v, interval)=> (+v >= interval[0] && +v <= interval[1]) ? 0 : ['limit', interval],
 equal: (v, target)=>{       //和什么相等
  var _list = document.getElementsByName(target), _target
  for(var i = 0;i < _list.length;i++){
  if(_list[i].className.indexOf('va') > -1){
   _target = _list[i];
  }
  }
  return (_target.value === v) ? 0 : ['equal', _target.getAttribute('tag')]
 },
 unique:(v)=>{
  var _list = document.getElementsByClassName('unique'),
   valList = [].map.call(_list, item=>item.value)
  return (unique(valList).length === valList.length) ? 0 : ['unique']
 }
 }

 for(var i = 0;i < conditions.length;i++){
 var condi = conditions[i],
  type = condi.type,
  typeVal = condi.typeVal
 res = cfg[type](v, typeVal)
 // console.log(res, v, type,typeVal)
 //如果有自定义报错信息, 返回自定义的报错信息
 console.log(res)
 if(res){
  res = condi.errMsg || res
  break
 }
 }

 return res;
}

function showErr(name, checkResult){
 var type = checkResult[0],
  ext = checkResult[1] || []

 var ERR_MSG = {
 nonvoid: `${name}不能为空`,
 reg: `${name}格式错误`,
 limit: `${name}必须在${ext[0]}与${ext[1]}之间`,
 equal: `两次${ext}不相同`,
 unique: `${name}重复`
 }
 //使用layer来报错,如果需要自定义报错方式,要把全文的layer集中起来包一层。
 layer.msgWarn(ERR_MSG[type])
}

/**
 * [VaConfig va配置的构造函数]
 * @param {[string]} type [校验类型,如reg, limit等等]
 * @param {[*]} typeVal  [根据校验类型配置的值]
 * @param {[string]} errMsg [报错信息]
 * @param {[string]} name [用以ajax的字段名]
 * @param {[string]} tag [中文名,用以报错]
 */
function VaConfig(type, typeVal, errMsg, name, tag){
 this.type = type, this.typeVal = typeVal, this.errMsg = errMsg, this.name = name, this.tag = tag
}
//用来剔除重复的规则,以及规则的覆盖。默认后面的取代前面
Array.prototype.uConcat = function(arr){
 var comb = this.concat(arr)
  ,unique = {}
  ,result = []

 for(var i = 0;i < comb.length;i++){
 // console.log(i, comb[i])
 var type = comb[i].type
 if(unique[type]){
  var index = unique[type].index
  unique[type] = comb[i]
  unique[type].index = index;
 }else{
  unique[type] = comb[i]
  unique[type].index = i;
 }
 }

 for(var i= 0;i < 100;i++){
 for(var item in unique){
  if(unique[item].index === i){
  delete unique[item].index
  result.push(unique[item])
  }
 }
 }
 return result
}

//正则表
var regList = {
 ImgCode: /^[0-9a-zA-Z]{4}$/,
 SmsCode: /^\d{4}$/,
 MailCode: /^\d{4}$/,
 UserName: /^[\w|\d]{4,16}$/,
 Password: /^[\w!@#$%^&*.]{6,16}$/,
 Mobile: /^1[3|5|8]\d{9}$/,
 RealName: /^[\u4e00-\u9fa5 ]{2,10}$/,
 BankNum: /^\d{10,19}$/,
 Money: /^([1-9]\d*|0)$/,
 Answer: /^\S+$/,
 Mail: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/
}

va.install = function(Vue, options){
 Vue.directive('va',{
 bind:function(el, binding, vnode){
  var vm = vnode.context
  ,name = binding.arg === 'EXTEND' ? el.getAttribute('name') : binding.arg
  ,tag = el.getAttribute('tag')
  ,baseCfg = []     //默认的校验规则    --不用写,默认存在的规则(如非空)
  ,optionalConfig = []    //用户选择的配置成套  --与name相关
  ,customConfig = []     //用户自定义的规则(组件中) --bingding.value
  ,option = binding.modifiers
  ,regMsg = el.getAttribute('regMsg') || ''

  var eazyNew = (type, typeVal) =>{return new VaConfig(type, typeVal, '', name, tag)}
  var regNew = (typeVal) =>{return new VaConfig('reg', typeVal, regMsg, name, tag)} //正则的新建

  el.className = 'va' + vm._uid
  el.name = name

  vm.vaConfig || (vm.vaConfig = {})
  var NON_VOID = eazyNew('nonvoid', true)

  //默认非空,如果加了canNull的修饰符就允许为空
  if(!option.canNull){
  baseCfg.push(NON_VOID)
  }

  //需要立即校验的框
  if(option.vanow){
  el.addEventListener('change', function(){
   vm.vaResult || (vm.vaResult = {})
   vm.vaVal || (vm.vaVal = {})
   var value = el.value,
    conditions = vm.vaConfig[name],
    para = el.getAttribute('va-para')  //传给回调的参数

   //如果允许为空的此时为空,不校验
   if(value === '' && option.canNull){
   vm.vaVal[name] = value
   return
   }

   vm.vaResult[name] = check(value, conditions);
   var _result = vm.vaResult[name]
   if(_result){
   //如果返回的是字符串,则为自定义报错; 如果是数组,则使用showErr 报错
   typeof _result === 'string' ? layer.msgWarn(_result) : showErr(conditions[0].tag, _result)
   el.value = vm.vaVal[name] = ''
   return
   }
   vm.vaVal[name] = value
   vm.$vanow(para)   //写在实例内部method的回调
  })
  }

  //不能重复的
  if(option.unique){
  optionalConfig.push(eazyNew('unique', name))
  }

  //如果有在正则表里
  var regOptions = Object.keys(option);
  for(var i = 0;i < regOptions.length;i++){
  var regOption = regOptions[i]
  if(regList[regOptions[i]]){
   optionalConfig.push(regNew(regList[regOption]))
  }
  }

  //如果regList里有name对应的,直接就加进optionalConfig
  if(regList[name]){
  optionalConfig.push(regNew(regList[name]))
  }

  //用户自定义的规则
  if(binding.value){
  customConfig = binding.value.map(item=>{
   let type = Object.keys(item)[0];
   if(type === 'reg'){
   return regNew(item[type])
   }else{
   if(type === 'unique'){
    addClass(el, 'unique')
   }
   return eazyNew(type, item[type])
   }
  })
  }

  //规则由 默认规则 + 修饰符规则 + 写在属性的自定义规则 + 用户直接加到vm.vaConfig里的规则 合并(后面的同type规则会覆盖前面的)
  vm.vaConfig[name] || (vm.vaConfig[name] = [])
  vm.vaConfig[name] = baseCfg.uConcat(optionalConfig).uConcat(customConfig).uConcat(vm.vaConfig[name])
 },
 })

 Vue.directive('va-check', {
 bind:function(el, binding, vnode){
  var vm = vnode.context
  el.addEventListener('click', function(){
  var domList = document.getElementsByClassName('va' + vm._uid);
  vm.vaResult || (vm.vaResult = {})
  vm.vaVal || (vm.vaVal = {})

  for(var i = 0;i < domList.length;i++){
   var dom = domList[i],
    name = dom.name,
    value = dom.value,
    conditions = vm.vaConfig[name]

   var _result = check(value, conditions)
   //如果返回不为0,则有报错
   if(_result){
   //如果返回的是字符串,则为自定义报错; 如果是数组,则使用showErr 报错
   typeof _result === 'string' ? layer.msgWarn(_result) : showErr(conditions[0].tag, _result)
   return
   }
   vm.vaVal[name] = value
  }
  //校验通过的回调
  vm.$vaSubmit()
  // layer.msgWarn('全部校验成功')
  console.log(vm.vaVal)
  })

 }
 })

 Vue.directive('va-test',{
 bind: function(el, binding, vnode){
  var vm = vnode.context
  el.addEventListener('click', function(){
  vm.vaResult || (vm.vaResult = {})
  vm.vaVal || (vm.vaVal = {})

  var dom = document.getElementsByName(binding.arg)[0],
   name = dom.name,
   value = dom.value,
   conditions = vm.vaConfig[name]

  var _result = check(value, conditions)
  //如果返回不为0,则有报错
 console.log(_result)
  if(_result){
   //如果返回的是字符串,则为自定义报错; 如果是数组,则使用showErr 报错
   typeof _result === 'string' ? layer.msgWarn(_result) : showErr(conditions[0].tag, _result)
   return
  }

  vm.vaVal[name] = value
  var callback = Object.keys(binding.modifiers)[0]
  vm[callback]()
  })
 }
 })


 /**
 ** 在实例的monuted周期使用 api设置自定义配置
 */
 Vue.prototype.VaConfig = VaConfig
}

module.exports = va

现在项目已经用起来了。当然表单验证这种是高度定制化的。纯粹分享个过程和思路。也算我这个vue新手的一次阶段性成果吧。哈哈~

使用实例

Vue表单验证插件的制作过程

第一个框,加了两条指令

1.v-va:Password 这个代表使用配置表中password对应的配置(包括非空和正则,默认规则),同时应用Password作为校验成功获取的 数据对象的key
2.tag为报错显示中此输入框的名字

第二个框,为确认框,也加了两个指令
1.v-va:checkPassword.Password = "[{'equal':'Password'}]"
一般v-va后面的第一个字段为数据对象的key,他和正则对应的名字有可能不同。
这个字段如果和配置表中的配置匹配,那么自然应用配置。
如果不匹配,就要自己在后面用.的方式加配置(选项规则)。像这里的Password。

最后面还有一个 属性值 "[{'equal':'Password'}]"(自定义规则)。
这个地方用了数组,即会按这个数组的配置来进行校验。
同时这个数组有顺序,顺序代表规则的优先级。
这个配置代表,这个框必须和上面那个Password的框值相等,否则报错。
另外确认框不加入最后的结果数据对象。

2.tag 用来作为报错信息的名字

校验触发按钮 上面有一个指令 v-va-check
1.用来触发校验
2.校验成功后,将数据对象存在实例的vaVal属性下

根据上面的实例

规则的优先级:
1.自定义规则 > 选项规则 > 默认规则
2.规则中的优先级依照数组顺序

另外,可以看到为了使用者方便,我在我们团队中事先做了一些约定,并可能会用到 v-va、v-va-check、tag等指令,占用了实例的两个属性名vaConfig、vaVal。这些约定和设置可以使使用者使用方便(通过配置控制校验时机, 校验成功后自然生成通过的数据对象,自定义报错信息等等)。但是也减少了这个插件的普适性。

此方案仅提供各位做思路参考。个人认为,表单验证是高度定制化的需求,尽量根据各个业务情况进行取舍。在我的方案中,并不像vue-validator一样做了脏校验。

本文已被整理到了《Vue.js前端组件学习教程》,欢迎大家学习阅读。

关于vue.js组件的教程,请大家点击专题vue.js组件学习教程进行学习。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Notify - 基于jquery的消息通知插件
Oct 18 Javascript
jquery 自定义容器下雨效果可将下雨图标改为其他
Apr 23 Javascript
探析浏览器执行JavaScript脚本加载与代码执行顺序
Jan 12 Javascript
基于RequireJS和JQuery的模块化编程——常见问题全面解析
Apr 14 Javascript
JavaScript中输出信息的方法(信息确认框-提示输入框-文档流输出)
Jun 12 Javascript
自定义vue全局组件use使用、vuex的使用详解
Jun 14 Javascript
React组件之间的通信的实例代码
Jun 27 Javascript
Vue2.0 slot分发内容与props验证的方法
Dec 12 Javascript
对Vue table 动态表格td可编辑的方法详解
Aug 28 Javascript
微信小程序Page中data数据操作和函数调用方法
May 08 Javascript
JS利用prototype给类添加方法操作详解
Jun 21 Javascript
简单了解Vue computed属性及watch区别
Jul 10 Javascript
JS中使用正则表达式g模式和非g模式的区别
Apr 01 #Javascript
如何选择jQuery版本 1.x? 2.x? 3.x?
Apr 01 #jQuery
微信小程序网络请求的封装与填坑之路
Apr 01 #Javascript
微信小程序 列表的上拉加载和下拉刷新的实现
Apr 01 #Javascript
Node.js之网络通讯模块实现浅析
Apr 01 #Javascript
vue-cli+webpack记事本项目创建
Apr 01 #Javascript
JS常见创建类的方法小结【工厂方式,构造器方式,原型方式,联合方式等】
Apr 01 #Javascript
You might like
让PHP以ROOT权限执行系统命令的方法
2011/02/10 PHP
php 日期和时间的处理-郑阿奇(续)
2011/07/04 PHP
php中echo()和print()、require()和include()等易混淆函数的区别
2012/02/22 PHP
jQuery EasyUI API 中文文档 - ProgressBar 进度条
2011/09/29 Javascript
jQuery方法简洁实现隔行换色及toggleClass的使用
2013/03/15 Javascript
JAVASCRIPT函数作用域和提前声明 分享
2013/08/22 Javascript
jQuery EasyUI之DataGrid使用实例详解
2016/01/04 Javascript
RGB和YUV 多媒体编程基础详细介绍
2016/11/04 Javascript
AngularJS中$apply方法和$watch方法用法总结
2016/12/13 Javascript
Angular+Node生成随机数的方法
2017/06/16 Javascript
JS使用正则表达式验证身份证号码
2017/06/23 Javascript
使用AngularJS对表单提交内容进行验证的操作方法
2017/07/12 Javascript
Node中使用ES6语法的基础教程
2018/01/05 Javascript
JS实现获取毫秒值及转换成年月日时分秒的方法
2018/08/15 Javascript
vue加载完成后的回调函数方法
2018/09/07 Javascript
Vue 样式绑定的实现方法
2019/01/15 Javascript
JavaScript的Proxy可以做哪些有意思的事儿
2019/06/15 Javascript
koa2 从入门到精通(小结)
2019/07/23 Javascript
使用cx_freeze把python打包exe示例
2014/01/24 Python
深入讲解Python中面向对象编程的相关知识
2015/05/25 Python
Python 基础教程之闭包的使用方法
2017/09/29 Python
Python中getpass模块无回显输入源码解析
2018/01/11 Python
Python中判断输入是否为数字的实现代码
2018/05/26 Python
python3 读取Excel表格中的数据
2018/10/16 Python
pytorch forward两个参数实例
2020/01/17 Python
在tensorflow中设置保存checkpoint的最大数量实例
2020/01/21 Python
python打开文件的方式有哪些
2020/06/29 Python
解决pip install psycopg2出错问题
2020/07/09 Python
python 模拟登陆163邮箱
2020/12/15 Python
CSS3 background-image颜色渐变的实现代码
2018/09/13 HTML / CSS
英国健身超市:Fitness Superstore
2019/06/17 全球购物
心理学专业毕业生推荐信范文
2013/11/21 职场文书
数学系毕业生求职信
2014/05/29 职场文书
学校领导班子对照检查材料
2014/09/24 职场文书
工作表扬信范文
2015/01/17 职场文书
2015年复活节活动总结
2015/02/27 职场文书