任何表單驗證模塊都是由 配置——校驗——報錯——取值 這幾部分構成的。vue
配置: 配置規則 和配置報錯,以及優先級node
校驗: 有在 change 事件校驗, 在點擊提交按鈕的時候校驗, 固然也有在input事件取值的ajax
報錯: 報錯方式通常要分,報錯的文字有模板,也有自定義的後端
取值: 將經過驗證的數據返還給開發者調用數組
下面是我老大針對公司項目給我提出的要求app
集中式的管理 校驗規則 和 報錯模板。dom
報錯時機可選函數
校驗正確後的數據,已經打包成對象,能夠直接用ui
容許各個頁面對規則進行覆蓋,對報錯信息進行自定義修改,以及容許ajax獲取數據後,再對規則進行補充this
按順序來校驗,在第一個報錯的框彈出錯誤
我就很好奇地問, 爲何要這樣子呢?而後老大就跟我一條一條解答:
集中式管理規則,和報錯模板的好處,就是規則能夠全局通用,一改全改。老大跟我說,光是暱稱的正則就改了三次。若是這些正則寫在各個頁面,o( ̄ヘ ̄o#)哼,你就要改N個頁面了
pc和移動的流程不同,pc不少校驗都要在change事件或者input事件就校驗並報錯了,而移動則通常是要到提交按鈕再進行校驗。因此寫插件的時候要作好兩手準備。而後,報錯用的ui要能夠支持咱們如今用的layer插件。固然之後這個報錯的ui也可能變,因此你懂滴。
固然原來jq時代,咱們的公用表單驗證,就能驗證完了,把數據都集合到一個對象裏。這樣ajax的時候,就不用再去取值了。你這個插件耶要達到這個效果
原來jq的那個公用腳本,正則和報錯都集中到一個地方去了,在不少地方已經很方便了。可是在一些頁面須要改東西的時候還不夠靈活。像RealName這個規則,最先是針對某個頁面配置的,用的是後端接口上的字段名。另外一個支付頁,後端接口上的字段名改爲了PayUser了,可是正則仍是RealName的,原來咱們是要複寫一下RealName。這個就不太方便也很差看了。另一個,支付金額,有最大值和最小值的限制,這個須要從後端獲取的。你也要考慮這個狀況。要作到各個頁面上也能有一些靈活的地方能夠修改規則,自定義報錯等等。
爲何要按順序校驗啊?你忘了上次牛哥讓咱們輸入框,從上到下,按順序報錯。否則用戶都不知道哪一個地方錯了。還有規則也是要按順序的。哦哦哦。看來此次我放東西的時候,要用下數組了。儘可能保持順序。
我聽了以後,大體懂了,原來以前本身寫的jq表單驗證還有這麼多不舒服的點。-_-|||
接下來,是看看vue給個人好東西。讓我來寫
我一個vue小白,怎麼就開始寫vue插件了呢?那是由於想解決方案的時候,翻Vue文檔翻到了這裏
這些東東,等我寫完va.js的時候,感受尤大寫的真的是很清楚了。
其實我是想寫個指令來完成表單驗證的事的。結果發現可能有2-3個指令,並且要再Vue.prototype上定義些方法,好讓各個子實例內部也能拓展規則。因而老大說,這就至關於插件了。這讓我非常吃鯨。
Vue 文檔真的寫得很用心,可是我再補充一點吧
vnode.context 就是Vue的實例
咱們作項目的時候,常常一個根組件上掛着N個子組件,子組件上又可能掛着N個子組件。vnode.context獲取的實例,是綁定該指令的組件的實例。這個就至關好用了。你能夠作不少事情
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 }
type: nonvoid(非空), reg(正則), limit(區間), equal(與某個input相等),unique(不能相同)
typeVal: 根據不一樣type設置不一樣的值
errMsg: 自定義的報錯信息
name: 用來傳ajax的字段,如Password, Username
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新手的一次階段性成果吧。哈哈~
第一個框,加了兩條指令
v-va:Password 這個表明使用配置表中password對應的配置(包括非空和正則,默認規則),同時應用Password做爲校驗成功獲取的 數據對象的key
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同樣作了髒校驗