本身写表单校验插件

1、表单验证模块的构成

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

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

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

  3. 报错: 报错方式通常要分,报错的文字有模板,也有自定义的后端

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

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

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

  2. 报错时机可选函数

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

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

  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给个人好东西。让我来写

2、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调用的

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

核心思路以下图:

  • 规则的构造函数

//va配置的构造函数 function VaConfig(type, typeVal, errMsg, name, tag){ this.type = type, this.typeVal = typeVal, this.errMsg = errMsg, this.name = name, this.tag = tag }
  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(正则),那么会互相覆盖。 
覆盖的优先级: 自定义规则 > 选项规则 > 默认规则

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

源码

var Vue
var checkWhenChange = true  //每一个输入框须要离焦即校验

// 给一个dom添加class
function addClass(dom, className){
  // if (dom.classList){
  //   dom.classList.add(className);
  // }else{
  //   dom.className += ' ' + className;
  // }

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

//经常使用正则表
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|4|5|7|8]\d{9}$/,
  RealName: /^[\u4e00-\u9fa5|·]{2,16}$|^[a-zA-Z|\s]{2,20}$/,
  BankNum: /^\d{10,19}$/,
  Money: /^([1-9]\d*|[0-9]\d*\.\d{1,2}|0)$/,
  Answer: /^\S+$/,
  Mail: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/
}

// 断言函数
function assert(condition, message){
  if(!condition){
    console.error('[va-warn]:' + message)
  }
}

// Rule构造器
function Rule(ruleType, ruleValue, errMsg){
  this.ruleType = ruleType
  this.ruleValue = ruleValue
  this.errMsg = errMsg || ''
}

//VaForm构造器
function VaForm(el, finalRules, modifiers){
  this.ruleOrder = []
  this.rules = {}
  this.dom = el
  this.value = el.value   //值的副本
  this.validated = false  //是否被验证过
  this.tag = el.getAttribute('tag')   //提示的字段名
  // this.correctMsg = `${this.tag}输入正确!`
  this.correctMsg = ''
  this.modifiers = modifiers   //一些特殊的配置
  this.noCheck = false         //为true则不要校验

  this.ruleOrder = finalRules.map(item=>{
    this.rules[item.ruleType] = item
    return item.ruleType
  })
}

//rules中靠前的配置优先级最高
function mergeRule(...rules){
  var mergeResult = []
  var combineArr = Array.prototype.concat.apply([], rules)
  var hash = {}
  combineArr.forEach((rule)=>{
    if(hash[rule.ruleType] === undefined){
      mergeResult.push(rule)
      hash[rule.ruleType] = mergeResult.length - 1
    }else{
      var index = hash[rule.ruleType]
      Object.assign(mergeResult[index], rule)
    }
  })
  return mergeResult
}

//单个规则的验证结果
function VaResult(ruleType, ruleValue, isPass, errMsg){
  this.ruleType = ruleType
  this.ruleValue = ruleValue
  this.isPass = isPass
  this.errMsg = errMsg
}

// 显示结果的构造器
function DisplayResult(isPass, message){
  this.isPass = isPass
  this.message = message
}

//单个规则的校验,或者单个表单的校验
function validate(field, ruleType){
  assert(field, '未输入要验证的字段')
  var vaForm = this.forms[field]
  var {ruleOrder, rules} = vaForm

  if(ruleType === undefined){
    return this.checkForm(vaForm)
  }else{
    var rule = rules[ruleType] //规则
    return this.checkRule(vaForm, rule)
  }
  // vaForm.validated = true
}

// 得到不一样的报错信息
function getErrMsg(vaForm, ruleType, ruleValue){
  var tag = vaForm.tag
  var errMsgs = {
    NonEmpty: `${tag}不能为空`,
    reg: `${tag}格式错误`,
    limit: `${tag}必须在${ruleValue[0]}与${ruleValue[1]}之间`,
    equal:`两次${tag}不相同`,
    length: `${tag}长度必须在${ruleValue[0]}与${ruleValue[1]}之间`,
    unique: `${tag}不能相同`
  }
  return errMsgs[ruleType]
}

//检测非空
function checkEmpty(ruleValue, vaForm, va){
  return vaForm.value.trim() ? true : false
}
//检测正则
function checkReg(ruleValue, vaForm, va){
  return ruleValue.test(vaForm.value) ? true : false
}
//检测数字区间
function checkLimit(ruleValue, vaForm, va){
  var value = vaForm.value
  return ((+value >= ruleValue[0]) && (+value <= ruleValue[1])) ? true : false
}
//检测相等
function checkEqual(ruleValue, vaForm, va){
  var target = va.forms[ruleValue]
  return target.value === vaForm.value ? true : false
}
//检测字符长度
function checkCharLength(ruleValue, vaForm, va){
  var length = vaForm.value.length
  return ((+length >= ruleValue[0]) && (+length <= ruleValue[1])) ? true : false
}

//几个输入框要各不相同
function checkUnique(ruleValue, vaForm, va){
  var uniqueGroup = va.uniqueGroup[ruleValue]
  var values = uniqueGroup.map(field=>va.forms[field].value)
  var uniqueValues = values.filter((item,index,arr)=>arr.indexOf(item) === index)
  return values.length === uniqueValues.length ? true : false
}

// 检测单个规则
function checkRule(vaForm, rule){
  var forms = this.forms
  var {ruleType, ruleValue, errMsg} = rule
  //若是有自定义报错就按自定义报错,没有就格式化报错
  errMsg = errMsg || getErrMsg(vaForm, ruleType, ruleValue)

  var ruleCheckers = {
    NonEmpty: checkEmpty,
    reg: checkReg,
    limit: checkLimit,
    equal: checkEqual,
    length: checkCharLength,
    unique: checkUnique
  }

  var ruleChecker = ruleCheckers[ruleType]
  var isPass = ruleChecker(ruleValue, vaForm, this)
  var vaResult = new VaResult(ruleType, ruleValue, isPass, isPass ? null : errMsg)
  return vaResult
}

//检测单个表单
function checkForm(vaForm){
  var results = vaForm.ruleOrder.map(ruleType=>{
    var rule = vaForm.rules[ruleType]
    return this.checkRule(vaForm,rule)
  })

  var errIndex = null
  for(var i = 0;i < results.length;i++){
    var result = results[i]
    if(result.isPass === false){
      errIndex = i
      break
    }
  }

  if(errIndex === null){
    return new DisplayResult(true,  vaForm.correctMsg)
  }else{
    return new DisplayResult(false, results[errIndex].errMsg)
  }
}

//刷新vaForm中的值的数据
function refreshValue(field, newValue){
  this.forms[field].value = newValue + ''
}

//更新全部表单的值
function refreshAllValue(){
  this.fieldOrder.forEach(field=>{
    var vaForm = this.forms[field]
    vaForm.value = vaForm.dom.value
  })
}

// 校验全部的表单,并弹出第一个错误。考虑能够为空的状况
function checkAll(){
  var firstErr = null
  this.fieldOrder.forEach(field=>{
    var vaForm = this.forms[field]
    var canNull = vaForm.ruleOrder.every(ruleType=>ruleType !== 'NonEmpty')  //输入框能够为空
    var noCheckEmpty = (vaForm.value === '' && canNull)   //该输入框能够为空,且输入为空

    if(vaForm.noCheck === false && noCheckEmpty === false){
      var result = this.setVmResult(field)
      // var result = this.validate(field)
      // this.vmResult[field] = result
      // vaForm.validated = true

      if(firstErr === null && result.isPass === false){
        firstErr = result.message
      }
    }

  })
  return firstErr
}

//验证单个字段,返回值,并弹出报错
function setVmResult(field){
  var result = this.validate(field) //本输入框结果
  this.vmResult[field] = result    //将报错弹出
  this.forms[field].validated = true  //校验过了
  return result
}

// 返回各个表单的值对象
function getValue(){
  var dataSet = {}
  for(var field in this.forms){
    dataSet[field] = this.forms[field].value
  }
  return dataSet
}

//添加一个规则
function addRule(field, index, Rule){
  var vaForm = this.forms[field]
  vaForm.ruleOrder.splice(index, 0, Rule.ruleType)
  vaForm.rules[Rule.ruleType] = Rule
}

// function resetAll(){
//   this.fieldOrder.forEach(field=>{
//     this.refreshValue(field, '')
//   })
// }

// 设置不校验的表单
function setNoCheck(field, bool){
  this.forms[field].noCheck = bool
}

function createVa(vm, field){
  var va = {
    vmResult:vm.va,
    fieldOrder:[],
    forms:{},
    group:{
      base:[],
    },
    equalGroup:{},                  //必须相等的字段
    uniqueGroup:{},                 //必须不一样的字段
    Rule:Rule,                      //Rule构造器
    VaForm:VaForm,                  //VaForm构造器
    validate: validate,             //暴露的校验函数
    setVmResult: setVmResult,       //校验并报错
    checkRule: checkRule,           //内部的校验单条规则的函数
    checkForm: checkForm,           //内部的校验单个表单的函数
    refreshValue: refreshValue,     //更新某个表单的值
    checkAll: checkAll,             //检查全部的函数
    getValue: getValue,             //获取全部表单的当前值,获得一个对象
    setNoCheck:setNoCheck,          //设置为不校验
    addRule:addRule,                //给一个表单添加一个规则
    refreshAllValue:refreshAllValue //更新全部表单的值
    // resetAll: resetAll
  }

  if(vm.$va){
    return vm.$va
  }else{
    vm.$va = va
    return va
  }
}

//v-va:Password.canNull = "[{reg:/^\d{4}$/}]"
//arg = Password,  modifiers.canNull = true, value为后面相关的
//arg用来存字段名, modifiers用来存特殊配置, value为规则, tag是中文提示名, group 为分组
var main = {}
main.install = function(_Vue, options){
  Vue = _Vue

    Vue.directive('va',{
    bind:function(el, binding, vnode){
      var vm = vnode.context                         //当前的vue实例
      var field = binding.arg === 'EXTEND' ? el.getAttribute('name') : binding.arg // 当arg为EXTEND,从name属性得到值
      var option = binding.modifiers                    //特殊配置(容许非空,编辑新增共用等)
      var value = el.value                              //输入框的初始值
      var group = el.getAttribute('group') || 'base'    //分组,一个表单框在多个组呢?这个还没设,要兼容。 经过相似 'group1 group2 group3 group4'
      var tag = el.getAttribute('tag')
      var regMsg = el.getAttribute('regMsg') || ''   //针对正则的自定义报错
      var baseRule = []                              //默认的校验规则             --不用写,默认存在的规则(如非空),优先级最高
      var customRule = []                            //用户自定义的规则(组件中) --bingding.value
      var optionalRule = []                          //配置项中引伸出来的规则,优先级最低

      assert(tag, '未设置输入框的tag')
      assert(vm.va, '实例的data选项上,未设置va对象')  //实例上若是没有设置结果则报错。
      assert(field, '未设置输入框字段')
      var va = createVa(vm, field)  //单例模式建立va,绑定在vm上
      va.fieldOrder.push(field)     //字段的检验顺序
      va.group[group].push(field)   //分组
      var NonEmpty = new Rule('NonEmpty', true, '')
      //默认非空
      if(option.CanNull === undefined){
        baseRule.push(NonEmpty)
      }

      //若是regList里有name对应的,直接就加进optionalConfig
      if(regList[field]){
        optionalRule.push(new Rule('reg', regList[field], regMsg))
      }

      //若是modefiers中的字段有在正则表里,将其加入optionalRule
      var regOptions = Object.keys(option);
      for(var i = 0;i < regOptions.length;i++){
        var regOption = regOptions[i]
        if(regList[regOptions[i]]){
          optionalRule.push(new Rule('reg', regList[regOption], regMsg))
        }
      }

      //用户自定义的规则
      if(binding.value !== undefined){
        customRule = binding.value.map(item=>{
          var ruleType = Object.keys(item)[0];
          var errMsg = ruleType === 'reg' ? regMsg : ''
          return new Rule(ruleType, item[ruleType], errMsg)
        })
      }

      var finalRules = mergeRule(baseRule, optionalRule, customRule)
      var hasUniqueRule = false
      //对联合校验的进行预处理
      finalRules.forEach(rule=>{
        var {ruleType, ruleValue} = rule
        if(ruleType === 'equal'){
          if(va.equalGroup[ruleValue] === undefined){
            va.equalGroup[ruleValue] = [field]
          }else{
            va.equalGroup[ruleValue].push(field)
          }
        }

        if(ruleType === 'unique'){
          hasUniqueRule = ruleValue
          if(va.uniqueGroup[ruleValue] === undefined){
            va.uniqueGroup[ruleValue] = [field]
          }else{
            va.uniqueGroup[ruleValue].push(field)
          }
        }
      })

      var vaForm = new VaForm(el, finalRules, option)
      va.forms[field] = vaForm

      if(checkWhenChange){
        function validateSingle(){
          va.refreshValue(field, el.value)  //更新值
          //若是容许为空的此时为空,不校验
          if(vaForm.value === '' && option.CanNull){
            va.vmResult[field] = {}   //若是为空,把界面显示上面的提示清掉
            return
          }

          if(vaForm.noCheck === false){
            va.setVmResult(field)
          }

          var isEqualTarget = false
          for(var index in va.equalGroup){
            if(index === field){
              isEqualTarget = true
            }
          }

          //相等框的联合校验
          if(isEqualTarget){
            va.equalGroup[field].forEach(item=>{va.setVmResult(item)})
          }

          //不一样框的联合校验
          if(hasUniqueRule){
            va.uniqueGroup[hasUniqueRule].forEach(item=>{va.setVmResult(item)})
          }
        }

        //在change和blur上都绑定了处理事件
        el.addEventListener('change', validateSingle)
        el.addEventListener('blur', validateSingle)
      }

    },
  })
}

export default main

 

如今项目已经用起来了。固然表单验证这种是高度定制化的。纯粹分享个过程和思路。也算我这个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同样作了脏校验

相关文章
相关标签/搜索