前段时间,老大搭好了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表单验证插件。下面介绍下思路。

一、表单验证模块的构成

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

  1. 配置: 配置规则 和配置报错,以及优先级

  2. 校验: 有在 change 事件校验, 在点击提交按钮的时候校验, 当然也有在input事件取值的

  3. 报错: 报错方式一般要分,报错的文字有模板,也有自定义的

  4. 取值: 将通过验证的数据返还给开发者调用

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

  1. 集中式的管理 校验规则 和 报错模板。

  2. 报错时机可选

  3. 校验正确后的数据,已经打包成对象,可以直接用

  4. 允许各个页面对规则进行覆盖,对报错信息进行自定义修改,以及允许ajax获取数据后,再对规则进行补充

  5. 按顺序来校验,在第一个报错的框弹出错误

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

  1. 集中式管理规则,和报错模板的好处,就是规则可以全局通用,一改全改。老大跟我说,光是昵称的正则就改了三次。如果这些正则写在各个页面,o( ̄ヘ ̄o#)哼,你就要改N个页面了

  2. pc和移动的流程不一样,pc很多校验都要在change事件或者input事件就校验并报错了,而移动则一般是要到提交按钮再进行校验。所以写插件的时候要做好两手准备。然后,报错用的ui要可以支持我们现在用的layer插件。当然以后这个报错的ui也可能变,所以你懂滴。

  3. 当然原来jq时代,我们的公用表单验证,就能验证完了,把数据都集合到一个对象里。这样ajax的时候,就不用再去取值了。你这个插件耶要达到这个效果

  4. 原来jq的那个公用脚本,正则和报错都集中到一个地方去了,在很多地方已经很方便了。但是在一些页面需要改东西的时候还不够灵活。像RealName这个规则,最早是针对某个页面配置的,用的是后端接口上的字段名。另一个支付页,后端接口上的字段名改成了PayUser了,但是正则还是RealName的,原来我们是要复写一下RealName。这个就不太方便也不好看了。另外一个,支付金额,有最大值和最小值的限制,这个需要从后端获取的。你也要考虑这个情况。要做到各个页面上也能有一些灵活的地方可以修改规则,自定义报错等等。

  5. 为什么要按顺序校验啊?你忘了上次牛哥让我们输入框,从上到下,按顺序报错。不然用户都不知道哪个地方错了。还有规则也是要按顺序的。哦哦哦。看来这次我放东西的时候,要用下数组了。尽量保持顺序。

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

二、Vue 的插件怎么写

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

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

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

va.js主要用的是 Vue指令

Vue 文档真的写得很用心,但是我再补充一点吧
vnode.context 就是Vue的实例
我们做项目的时候,经常一个根组件上挂着N个子组件,子组件上又可能挂着N个子组件。vnode.context获取的实例,是绑定该指令的组件的实例。这个就相当好用了。你可以做很多事情

当然还用了点Vue.prototype

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

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

核心思路如下图:

  • 规则的构造函数

  1. //va配置的构造函数

  2. function VaConfig(type, typeVal, errMsg, name, tag){

  3. this.type = type, this.typeVal = typeVal, this.errMsg = errMsg, this.name = name, this.tag = tag

  4. }

  1. type: nonvoid(非空), reg(正则), limit(区间), equal(与某个input相等),unique(不能相同)

  2. typeVal: 根据不同type设置不同的值

  3. errMsg: 自定义的报错信息

  4. name: 用来传ajax的字段,如Password, Username

  5. tag:用来报错的名字,如‘银行账号’,‘姓名’

设置了三种规则

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

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

源码

  1. /*

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

  3. */

  4. var va = {};

  5. function unique(arr){

  6. var hashTable = {}, newArr = [];

  7. for(var i = 0;i < arr.length;i++){

  8. if(!hashTable[arr[i]]){

  9. hashTable[arr[i]] = true;

  10. newArr.push(arr[i]);

  11. }

  12. }

  13. return newArr;

  14. }

  15. function addClass(dom, _class){

  16. var hasClass = !!dom.className.match(new RegExp('(\\s|^)' + _class + '(\\s|$)'))

  17. if(!hasClass){

  18. dom.className += ' ' + _class

  19. }

  20. }

  21. //校验函数

  22. function check(v, conditions){

  23. var res = 0; //0代表OK, 若为数组代表是某个字段的错误

  24. //验证函数

  25. var cfg = {

  26. //非空

  27. nonvoid: (v, bool)=>{

  28. if(bool){

  29. return v.trim() ? 0 : ['nonvoid'];

  30. }else{

  31. return 0;

  32. }

  33. },

  34. reg:(v, reg)=> reg.test(v) ? 0 : ['reg'], //正则

  35. limit:(v, interval)=> (+v >= interval[0] && +v <= interval[1]) ? 0 : ['limit', interval],

  36. equal: (v, target)=>{ //和什么相等

  37. var _list = document.getElementsByName(target), _target

  38. for(var i = 0;i < _list.length;i++){

  39. if(_list[i].className.indexOf('va') > -1){

  40. _target = _list[i];

  41. }

  42. }

  43. return (_target.value === v) ? 0 : ['equal', _target.getAttribute('tag')]

  44. },

  45. unique:(v)=>{

  46. var _list = document.getElementsByClassName('unique'),

  47. valList = [].map.call(_list, item=>item.value)

  48. return (unique(valList).length === valList.length) ? 0 : ['unique']

  49. }

  50. }

  51. for(var i = 0;i < conditions.length;i++){

  52. var condi = conditions[i],

  53. type = condi.type,

  54. typeVal = condi.typeVal

  55. res = cfg[type](v, typeVal)

  56. // console.log(res, v, type,typeVal)

  57. //如果有自定义报错信息, 返回自定义的报错信息

  58. console.log(res)

  59. if(res){

  60. res = condi.errMsg || res

  61. break

  62. }

  63. }

  64. return res;

  65. }

  66. function showErr(name, checkResult){

  67. var type = checkResult[0],

  68. ext = checkResult[1] || []

  69. var ERR_MSG = {

  70. nonvoid: `${name}不能为空`,

  71. reg: `${name}格式错误`,

  72. limit: `${name}必须在${ext[0]}与${ext[1]}之间`,

  73. equal: `两次${ext}不相同`,

  74. unique: `${name}重复`

  75. }

  76. //使用layer来报错,如果需要自定义报错方式,要把全文的layer集中起来包一层。

  77. layer.msgWarn(ERR_MSG[type])

  78. }

  79. /**

  80. * [VaConfig va配置的构造函数]

  81. * @param {[string]} type [校验类型,如reg, limit等等]

  82. * @param {[*]} typeVal [根据校验类型配置的值]

  83. * @param {[string]} errMsg [报错信息]

  84. * @param {[string]} name [用以ajax的字段名]

  85. * @param {[string]} tag [中文名,用以报错]

  86. */

  87. function VaConfig(type, typeVal, errMsg, name, tag){

  88. this.type = type, this.typeVal = typeVal, this.errMsg = errMsg, this.name = name, this.tag = tag

  89. }

  90. //用来剔除重复的规则,以及规则的覆盖。默认后面的取代前面

  91. Array.prototype.uConcat = function(arr){

  92. var comb = this.concat(arr)

  93. ,unique = {}

  94. ,result = []

  95. for(var i = 0;i < comb.length;i++){

  96. // console.log(i, comb[i])

  97. var type = comb[i].type

  98. if(unique[type]){

  99. var index = unique[type].index

  100. unique[type] = comb[i]

  101. unique[type].index = index;

  102. }else{

  103. unique[type] = comb[i]

  104. unique[type].index = i;

  105. }

  106. }

  107. for(var i= 0;i < 100;i++){

  108. for(var item in unique){

  109. if(unique[item].index === i){

  110. delete unique[item].index

  111. result.push(unique[item])

  112. }

  113. }

  114. }

  115. return result

  116. }

  117. //正则表

  118. var regList = {

  119. ImgCode: /^[0-9a-zA-Z]{4}$/,

  120. SmsCode: /^\d{4}$/,

  121. MailCode: /^\d{4}$/,

  122. UserName: /^[\w|\d]{4,16}$/,

  123. Password: /^[\w!@#$%^&*.]{6,16}$/,

  124. Mobile: /^1[3|5|8]\d{9}$/,

  125. RealName: /^[\u4e00-\u9fa5 ]{2,10}$/,

  126. BankNum: /^\d{10,19}$/,

  127. Money: /^([1-9]\d*|0)$/,

  128. Answer: /^\S+$/,

  129. Mail: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/

  130. }

  131. va.install = function(Vue, options){

  132. Vue.directive('va',{

  133. bind:function(el, binding, vnode){

  134. var vm = vnode.context

  135. ,name = binding.arg === 'EXTEND' ? el.getAttribute('name') : binding.arg

  136. ,tag = el.getAttribute('tag')

  137. ,baseCfg = [] //默认的校验规则 --不用写,默认存在的规则(如非空)

  138. ,optionalConfig = [] //用户选择的配置成套 --与name相关

  139. ,customConfig = [] //用户自定义的规则(组件中) --bingding.value

  140. ,option = binding.modifiers

  141. ,regMsg = el.getAttribute('regMsg') || ''

  142. var eazyNew = (type, typeVal) =>{return new VaConfig(type, typeVal, '', name, tag)}

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

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

  145. el.name = name

  146. vm.vaConfig || (vm.vaConfig = {})

  147. var NON_VOID = eazyNew('nonvoid', true)

  148. //默认非空,如果加了canNull的修饰符就允许为空

  149. if(!option.canNull){

  150. baseCfg.push(NON_VOID)

  151. }

  152. //需要立即校验的框

  153. if(option.vanow){

  154. el.addEventListener('change', function(){

  155. vm.vaResult || (vm.vaResult = {})

  156. vm.vaVal || (vm.vaVal = {})

  157. var value = el.value,

  158. conditions = vm.vaConfig[name],

  159. para = el.getAttribute('va-para') //传给回调的参数

  160. //如果允许为空的此时为空,不校验

  161. if(value === '' && option.canNull){

  162. vm.vaVal[name] = value

  163. return

  164. }

  165. vm.vaResult[name] = check(value, conditions);

  166. var _result = vm.vaResult[name]

  167. if(_result){

  168. //如果返回的是字符串,则为自定义报错; 如果是数组,则使用showErr 报错

  169. typeof _result === 'string' ? layer.msgWarn(_result) : showErr(conditions[0].tag, _result)

  170. el.value = vm.vaVal[name] = ''

  171. return

  172. }

  173. vm.vaVal[name] = value

  174. vm.$vanow(para) //写在实例内部method的回调

  175. })

  176. }

  177. //不能重复的

  178. if(option.unique){

  179. optionalConfig.push(eazyNew('unique', name))

  180. }

  181. //如果有在正则表里

  182. var regOptions = Object.keys(option);

  183. for(var i = 0;i < regOptions.length;i++){

  184. var regOption = regOptions[i]

  185. if(regList[regOptions[i]]){

  186. optionalConfig.push(regNew(regList[regOption]))

  187. }

  188. }

  189. //如果regList里有name对应的,直接就加进optionalConfig

  190. if(regList[name]){

  191. optionalConfig.push(regNew(regList[name]))

  192. }

  193. //用户自定义的规则

  194. if(binding.value){

  195. customConfig = binding.value.map(item=>{

  196. let type = Object.keys(item)[0];

  197. if(type === 'reg'){

  198. return regNew(item[type])

  199. }else{

  200. if(type === 'unique'){

  201. addClass(el, 'unique')

  202. }

  203. return eazyNew(type, item[type])

  204. }

  205. })

  206. }

  207. //规则由 默认规则 + 修饰符规则 + 写在属性的自定义规则 + 用户直接加到vm.vaConfig里的规则 合并(后面的同type规则会覆盖前面的)

  208. vm.vaConfig[name] || (vm.vaConfig[name] = [])

  209. vm.vaConfig[name] = baseCfg.uConcat(optionalConfig).uConcat(customConfig).uConcat(vm.vaConfig[name])

  210. },

  211. })

  212. Vue.directive('va-check', {

  213. bind:function(el, binding, vnode){

  214. var vm = vnode.context

  215. el.addEventListener('click', function(){

  216. var domList = document.getElementsByClassName('va' + vm._uid);

  217. vm.vaResult || (vm.vaResult = {})

  218. vm.vaVal || (vm.vaVal = {})

  219. for(var i = 0;i < domList.length;i++){

  220. var dom = domList[i],

  221. name = dom.name,

  222. value = dom.value,

  223. conditions = vm.vaConfig[name]

  224. var _result = check(value, conditions)

  225. //如果返回不为0,则有报错

  226. if(_result){

  227. //如果返回的是字符串,则为自定义报错; 如果是数组,则使用showErr 报错

  228. typeof _result === 'string' ? layer.msgWarn(_result) : showErr(conditions[0].tag, _result)

  229. return

  230. }

  231. vm.vaVal[name] = value

  232. }

  233. //校验通过的回调

  234. vm.$vaSubmit()

  235. // layer.msgWarn('全部校验成功')

  236. console.log(vm.vaVal)

  237. })

  238. }

  239. })

  240. Vue.directive('va-test',{

  241. bind: function(el, binding, vnode){

  242. var vm = vnode.context

  243. el.addEventListener('click', function(){

  244. vm.vaResult || (vm.vaResult = {})

  245. vm.vaVal || (vm.vaVal = {})

  246. var dom = document.getElementsByName(binding.arg)[0],

  247. name = dom.name,

  248. value = dom.value,

  249. conditions = vm.vaConfig[name]

  250. var _result = check(value, conditions)

  251. //如果返回不为0,则有报错

  252. console.log(_result)

  253. if(_result){

  254. //如果返回的是字符串,则为自定义报错; 如果是数组,则使用showErr 报错

  255. typeof _result === 'string' ? layer.msgWarn(_result) : showErr(conditions[0].tag, _result)

  256. return

  257. }

  258. vm.vaVal[name] = value

  259. var callback = Object.keys(binding.modifiers)[0]

  260. vm[callback]()

  261. })

  262. }

  263. })

  264. /**

  265. ** 在实例的monuted周期使用 api设置自定义配置

  266. */

  267. Vue.prototype.VaConfig = VaConfig

  268. }

  269. module.exports = va

现在项目已经用起来了。当然表单验证这种是高度定制化的。纯粹分享个过程和思路。也算我这个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 表单验证插件的写作过程相关推荐

  1. 写一个简单易用可扩展vue表单验证插件(vue-validate-easy)

    写一个vue表单验证插件(vue-validate-easy) 需求 目标:简单易用可扩展 如何简单 开发者要做的 写了一个表单,指定一个name,指定其验证规则. 调用提交表单方法,可以获取验证成功 ...

  2. vue表单验证插件 vuerify

    表单验证是一个网站或者系统不可或缺的,也是非常重要的一环.使用人工匹配验证的话会非常的复杂,尤其对于大量表单验证更是如此. 当然有一些jQuery 表单验证的插件,或许用着还不错.但是,如果你不想在v ...

  3. bootstrap的表单验证 vue_vue-form(vue表单验证插件 vue2.2+) 使用指南

    vue-form 是一个在vue 中用于表单验证的插件 目前版本为4.1.1 ??Form validation for?Vue.js 2.2+ import VueForm from 'vue-fo ...

  4. 10个强大的Javascript表单验证插件推荐

    创建一个JavaScript表单验证插件,可以说是一个繁琐的过程,涉及到初期设计.开发与测试等等环节.实际上一个优秀的程序员不仅是技术高手,也应该是善假于外物的.本文介绍了10个不错的JavaScri ...

  5. jQuery 表单验证插件,jQuery Validation Engine用法详解

    jQuery 表单验证插件,jQuery Validation Engine用法详解 功能强大的 jQuery 表单验证插件,适用于日常的 E-mail.电话号码.网址等验证及 Ajax 验证,除自身 ...

  6. jquery validate表单验证插件

    1 表单验证的准备工作 在开启长篇大论之前,首先将表单验证的效果展示给大家.     1.点击表单项,显示帮助提示 2.鼠标离开表单项时,开始校验元素  3.鼠标离开后的正确.错误提示及鼠标移入时的帮 ...

  7. html中表单的校验的插件,功能强大的jquery.validate表单验证插件

    本文实例为大家分享了jquery.validate表单验证的使用方法,供大家参考,具体内容如下 1 .表单验证的准备工作 在开启长篇大论之前,首先将表单验证的效果展示给大家. 1.点击表单项,显示帮助 ...

  8. 前端框架Vue(11)——Vue+表单验证 VeeValidate 实践

    这次来讲一下表单验证插件 VeeValidate,那为什么要结合 Vue 来讲呢?来看一下 github 上的图片就明白了! 为 Vue 而来的!不清楚是不是 Vue 的官方表单验证插件. 废话不多说 ...

  9. (转)强大的JQuery表单验证插件 FormValidator使用介绍

    jQuery formValidator表单验证插件是客户端表单验证插件. 在做B/S开发的时候,我们经常涉及到很多表单验证,例如新用户注册,填写个人资料,录入一些常规数据等等.在这之前,页面开发者( ...

最新文章

  1. CentOS6.4 安装OpenResty和Redis 并在Nginx中利用lua简单读取Redis数据
  2. 树莓派 linux0.12,12 个可替代树莓派的单板机
  3. 26、HTML 区块
  4. JetBrains——账户登录错误(JetBrains Account Error:JetBrains Account connection error: www.jetbrains.com)解决方案
  5. outlook邮箱备份方法:
  6. 在iPad/iPhone上使用Firebug
  7. 关于音游,除了节奏大师,你还熟悉哪些?
  8. SSD目标检测(Single Shot MultiBox Detector)(一)
  9. 网易云升级服务(云函数)
  10. Android OpenGL ES纹理总结、纹理坐标系说明、使用代码示例
  11. 网络兼容性是什么意思
  12. Mac 上安装mysql
  13. [附源码]Python计算机毕业设计_旅游系统
  14. ubuntu下淘宝的使用
  15. ​包载紫杉醇的tpgs还原性白蛋白纳米粒/GA-HSA 藤黄酸人血清蛋白纳米粒​
  16. 奇数点偶数点fft的matlab,电子科大 数字信号处理实验2_FFT的实现
  17. pfSense book之DNS解析
  18. 用 HI3559A / Hi3519A 接入 BT1120或BT656视频
  19. 新网站如何提交链接让百度更快速的收录
  20. 总规、控规、修规、概念性规划之间的关系

热门文章

  1. 脏读、不可重复读、幻读
  2. 交通规划福尼斯法计算出行分布交通量
  3. mybatis嵌套循环map(高级用法)
  4. Termux 高级终端安装使用配置教程
  5. Android疯狂连连看游戏
  6. Unity Draw Call是什么? DrawCall的简单介绍
  7. ContextLoaderListener RequestContextListener
  8. 【Android Studio 3.5.3】安装编译环境,Android adb 安装及使用
  9. 打造“大质量”管理体系 汉能获国际市场“绿色通行证”
  10. 7200套非标机械设计自动化设备运行工厂工作流水生产线视频实拍