摘要:一表單驗證模塊的構成任何表單驗證模塊都是由配置校驗報錯取值這幾部分構成的。其實我是想寫個指令來完成表單驗證的事的。當然表單驗證這種是高度定制化的。
前言
前段時間,老大搭好了Vue的開發環境,于是我們愉快地從JQ來到了Vue。這中間做的時候,在表單驗證上做的不開心,看到vue的插件章節,感覺自己也能寫一個,因此就自己開始寫了一個表單驗證插件va.js。
當然為什么不找個插件呢? vue-validator呀。
我想了下,一個是表單驗證是個高度定制化的東西,這種網上找到的插件為了兼顧各個公司的需求,所以加了很多功能,這些我們不需要。事實證明,vue-validator有50kb,而我寫的va.js只有8kb。
另一個是,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文檔翻到了這里
這些東東,等我寫完va.js的時候,感覺尤大寫的真的是很清楚了。
其實我是想寫個指令來完成表單驗證的事的。結果發現可能有2-3個指令,而且要再Vue.prototype上定義些方法,好讓各個子實例內部也能拓展規則。于是老大說,這就相當于插件了。這讓我很是吃鯨。
va.js主要用的是 Vue指令Vue 文檔真的寫得很用心,但是我再補充一點吧
vnode.context 就是Vue的實例
我們做項目的時候,經常一個根組件上掛著N個子組件,子組件上又可能掛著N個子組件。vnode.context獲取的實例,是綁定該指令的組件的實例。這個就相當好用了。你可以做很多事情
Vue.prototype.$method 就是可以在各個組件上調用的方法。可以在組件內部用 this.$method調用的
## 三、具體實現的思路 ##
核心思路如下圖:
規則的構造函數
//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一樣做了臟校驗。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81023.html
摘要:今天就來介紹一下如何利用的自定義指令來開發一個表單驗證插件的過程。按照這種方式就能夠使用自己開發的這個表單校驗插件。這段時間在進行一個新項目的前期搭建,新項目框架采用vue-cli3和typescirpt搭建。因為項目比較輕量,所以基本沒有使用額外的ui組件,有時候我們需要的一些基礎組件我就直接自己開發了。今天就來介紹一下如何利用vue的自定義指令directive來開發一個表單驗證插件的過...
摘要:寫一個表單驗證插件需求目標簡單易用可擴展如何簡單開發者要做的寫了一個表單,指定一個,指定其驗證規則。調用提交表單方法,可以獲取驗證成功后的數據。 寫一個vue表單驗證插件(vue-validate-easy) 需求 目標:簡單易用可擴展 如何簡單 開發者要做的 寫了一個表單,指定一個name,指定其驗證規則。 調用提交表單方法,可以獲取驗證成功后的數據。 調用重置表單方法重置表單 自...
摘要:寫一個表單驗證插件需求目標簡單易用可擴展如何簡單開發者要做的寫了一個表單,指定一個,指定其驗證規則。調用提交表單方法,可以獲取驗證成功后的數據。 寫一個vue表單驗證插件(vue-validate-easy) 需求 目標:簡單易用可擴展 如何簡單 開發者要做的 寫了一個表單,指定一個name,指定其驗證規則。 調用提交表單方法,可以獲取驗證成功后的數據。 調用重置表單方法重置表單 自...
摘要:寫一個表單驗證插件需求目標簡單易用可擴展如何簡單開發者要做的寫了一個表單,指定一個,指定其驗證規則。調用提交表單方法,可以獲取驗證成功后的數據。 寫一個vue表單驗證插件(vue-validate-easy) 需求 目標:簡單易用可擴展 如何簡單 開發者要做的 寫了一個表單,指定一個name,指定其驗證規則。 調用提交表單方法,可以獲取驗證成功后的數據。 調用重置表單方法重置表單 自...
摘要:示例電話電話錯誤信息指示指令對應的表單控件的驗證結果。其主要是根據驗證的結果進行的值的變換。如果為空值則默認把所有帶有驗證的空間作為需要驗證對象。 cddv vue.js 表單驗證插件使用說明 版本:1.0.8-6 獲取 github:這里 npm安裝 npm i vue-cdd-validator --save yarn安裝 yarn add vue-cdd-validator 安裝...
閱讀 3034·2023-04-26 03:01
閱讀 3538·2023-04-25 19:54
閱讀 1592·2021-11-24 09:39
閱讀 1374·2021-11-19 09:40
閱讀 4250·2021-10-14 09:43
閱讀 2062·2019-08-30 15:56
閱讀 1490·2019-08-30 13:52
閱讀 1660·2019-08-29 13:05