原生js設計表單驗證外掛的思路分析
這幾天在做一個用原生js寫的專案,需要用到表單驗證的功能。因為之前公司專案中的表單驗證是寫在業務裡的,改起來特別的麻煩,就想自己寫一個表單驗證的小工具。本來想在網上找一個教程研究研究的,但沒找到太好的,最後決定自己研究吧。文中示例的程式碼都是我自己寫的demo,並沒有參考一些框架或者庫的原始碼,所以程式碼可能比較難看, 重點還是分析一下思路 ,後期我會把完善好的程式碼發到github上。
原則:
先說一下我自己寫的表單驗證工具的思路或者是原則吧:
- 驗證程式碼和業務程式碼分離
- 擴充套件性強
- 驗證規則和驗證邏輯分離
第一點沒什麼可說的,一般的工具都是這樣做的,而且自己也吃了不少高耦合的虧,所以我還是把它放出來了。我這個工具希望達到的目的是,開發者只需要關注哪些資料需要驗證,將資料傳入給工具直接獲得驗證結果,讓開發者更多的關注其他的業務。
第二點是希望開發者可以自定義驗證規則,畢竟內建的規則再多,也架不住一個奇葩需求。
第三點是驗證規則和驗證邏輯的分離,其實這一條更多的是為第二條服務的。
驗證邏輯
這張流程圖就是我這個工具驗證邏輯,最開始判斷的傳入值是否可以為空,如果可以為空再去驗證是否有其他的驗證規則,如果沒有就直接驗證通過了(一般來說,如果表單的值可以為空,大部分情況不會再存在其他規則了)。假如還有其他的驗證規則,就去迴圈驗證,一旦有一條規則沒通過,就算驗證失敗。
如果說輸入的內容不可以為空,就去迴圈驗證這些規則就好了,和上面同理,一旦有錯就算驗證失敗。
class Vaildation{}複製程式碼
這就是驗證工具的類,我們先不著急往下寫,先思考到底往類中傳什麼配置引數。
let vaild = [ { value:this.username, type: "使用者名稱", rules:[ "isDefine", { name:"limit", check: true, min:5, max:12 } ] }, { value:this.password, type: "密碼", rules:[ "isDefine", { name:"mix", check:true, }, { name:"limit", check: true, min:5, max:12 } ] } ]; 複製程式碼
這是我想傳到類中的配置(如果你自己想設計一個的話也可以用別的樣式,資料、物件都可以),它是一個數組,裡面包含了每一個需要驗證的物件,簡單介紹一下配置中的屬性:
value: 需要認證的資料的值,型別是string或者number type:傳入這個資料的名稱或者標籤,比如使用者名稱、密碼、郵箱或者電話等,型別是字串或者陣列 rules: 需要使用哪些驗證規則,型別是array。 rules有兩種寫法,一種是簡寫比如:['isDefine', 'limit'],意思是不能為空,且有字數限制,使用預設 的規則; 也可以自定義具體的規則比如:['isDefine', {name: limit, check:true, min:5, max:12}], check引數 的意思是使規則生效,如果你寫成false即使你寫上這個規則也不會生效,min和max就好理解了,就是限制具體的 字數,預設的是6-16個字元。複製程式碼
讓我們接著回到類的設計中:
class Vaildation{ constructor(){ this.isCheck = false; this.vaild_item = []; this.vaild; } } 複製程式碼
isCheck: 驗證是否通過,型別是布林值 vaild_item: 存放我們傳入類中的引數 vaild: 驗證成功或失敗時返回的訊息複製程式碼
接下來,讓我們來設計驗證規則rules:
class Vaildation{ constructor(){ ...... } rules(){ return { mix: (vaild) => { for(let i = 0; i < vaild.rules.length; i++){ if(vaild.rules[i] == "mix" || vaild.rules[i].name == "mix"){ if(vaild.rules[i].check){ if(!vaild.rules[i].newRules){ return new RegExp(/^[a-zA-Z0-9]*([a-zA-Z][0-9]|[0-9][a-zA-Z])[a-zA-Z0-9]*$/).test(vaild.value); }else{ return new RegExp(vaild.rules[i].newRules).test(vaild.value); } }else{ return true; } } } }, limit: (vaild) => { let min; let max; vaild.rules.forEach((item) => { if(item == "limit" || item.name == "limit"){ min = item.min || 6; max = item.max || 16; } }) if(vaild.value.length < min || vaild.value.length > max){ return false; }else{ return true; } }, isDefine: (vaild) => { return !!vaild.value.trim(); }, } } } 複製程式碼
rules方法中返回了一個規則物件,我這裡內建了兩個規則:
一個是"mix",規定輸入的內容是數字和字母的混合;
另一個"limit",規定輸入內容字元數量的限制;
它們的引數是之前配置陣列中的rules屬性的值,即 ["isDefine", "mix", "limit"]
,我們可以看到這些規則是支援傳入字串或者是物件的,傳入物件時還可以做一些額外的配置,比如當我們有一些奇葩需求比如“輸入的內容每隔3個字必須有一個字母(for god's sake,天殺的需求)”,我們可以在"mix"中加入新的正則表示式:
{ name:"mix", check:true, newRules: 新的正則表示式, },複製程式碼
當然,如果你有新的規則比如電話,郵箱之類的,你也可以在新建 Vaildation
例項後手動新增進去,方便自定義:
let vaildation = new Vaildation(); vaild.rules()[newRules] = function(vaild){ //new Rules }複製程式碼
有了規則,我們還需要錯誤提示,我這裡每個規則的提示都使用了和規則一樣的名稱,方便處理驗證邏輯時使用:
error(){ return { mix: (vaild)=>{ return vaild.type + "必須為字母和數字組合"; }, limit: (vaild) => { let min; let max; vaild.rules.forEach((item) => { if(item == "limit" || item.name == "limit"){ min = item.min || 6; max = item.max || 16; } }) return vaild.type + "位數為" + min + "-" + max + "位"; }, define: (vaild) => { return vaild.type + "不能為空"; } } }複製程式碼
處理錯誤的時候我們就用到配置中的type屬性了,當然也可以直接在error中寫死錯誤處理的文案,這些都無所謂拉。
最後我們要看的是驗證的邏輯處理:
check_result(check, err = null){ return { check: check, //驗證是否為空 err: err, //驗證失敗時的文案 } } check(){ for(let i = 0; i < this.vaild_item.length; i++){ //迴圈每一個配置 let vaild = this.vaild_item[i].rules.findIndex(function(val){ //查詢是否有isDefine規則 return val === "isDefine"; }); if(vaild > -1){ let rules_arr = this.vaild_item[i].rules; //如果存在isDefine規則,就把它去除,去處理剩下的規則 rules_arr.splice(vaild,1); if(rules_arr.length > 0){ for(let j = 0; j < rules_arr.length; j++){ for(let o in this.rules()){ if(rules_arr[j]['name'] == o && !this.rules()[rules_arr[j]['name']].call(this,this.vaild_item[i])){ //將傳入的配置規則和類中自帶的規則進行匹配,如果匹配上並且匹配失敗,返回驗證資訊 this.vaild = [this.check_result(false,this.error()[rules_arr[j]['name']].call(this,this.vaild_item[i]))]; this.isCheck = false; return this.vaild; }else{ this.isCheck = true; this.vaild = [this.check_result(true)]; } } } } }else{ if(this.vaild_item[i].rules.length > 0 && !!this.vaild_item[i].value){ let rules_arr = this.vaild_item[i].rules; for(let j = 0; j < rules_arr.length; j++){ //這部分處理邏輯和上面相同 for(let o in this.rules()){ if(rules_arr[j]['name'] == o && !this.rules()[rules_arr[j]['name']].call(this,this.vaild_item[i])){ this.vaild = [this.check_result(false,this.error()[rules_arr[j]['name']].call(this,this.vaild_item[i]))]; this.isCheck = false; return this.vaild; }else{ this.isCheck = true; this.vaild = [this.check_result(true)]; } } } } } } return this.vaild; } 複製程式碼
check方法是核心邏輯,實際上就是上面流程圖的內容,一些重要的地方我也備註了(實在抱歉,這部分程式碼寫的有點兒醜,demo...demo...)。
最後我來展示一下這個工具用起來大概是什麼樣子的:
//配置檔案 let vaild = [ { value:this.username, type: "使用者名稱", rules:[ "isDefine", { name:"limit", check: true, min:5, max:12 } ] }, { value:this.password, type: "密碼", rules:[ "isDefine", { name:"mix", check:true, }, { name:"limit", check: true, min:5, max:12 } ] } ]; let vaildation = new Vaildation(); //新建一個Vaildation例項 vaildation.check_items = vaild;//將配置傳給例項中的check_items let result = vaildation.check(); //ok,你已經拿到結果了 //噢,別忘了可以在result.check或者vaildation.isCheck中拿到是否成功的結果複製程式碼
這篇文章僅僅是我設計這個工具時的一個思路,可能還存在一堆問題,就先拋磚引玉一下,也希望大家能多多指正,最後感謝大家的收看。