原生js設計表單驗證插件的思路分析

這幾天在作一個用原生js寫的項目,須要用到表單驗證的功能。由於以前公司項目中的表單驗證是寫在業務裏的,改起來特別的麻煩,就想本身寫一個表單驗證的小工具。原本想在網上找一個教程研究研究的,但沒找到太好的,最後決定本身研究吧。文中示例的代碼都是我本身寫的demo,並無參考一些框架或者庫的源碼,因此代碼可能比較難看, 重點仍是分析一下思路,後期我會把完善好的代碼發到github上。

原則:

先說一下我本身寫的表單驗證工具的思路或者是原則吧:javascript

  • 驗證代碼和業務代碼分離
  • 擴展性強
  • 驗證規則和驗證邏輯分離

第一點沒什麼可說的,通常的工具都是這樣作的,並且本身也吃了很多高耦合的虧,因此我仍是把它放出來了。我這個工具但願達到的目的是,開發者只須要關注哪些數據須要驗證,將數據傳入給工具直接得到驗證結果,讓開發者更多的關注其餘的業務。java

第二點是但願開發者能夠自定義驗證規則,畢竟內置的規則再多,也架不住一個奇葩需求。git

第三點是驗證規則和驗證邏輯的分離,其實這一條更多的是爲第二條服務的。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個字符。複製代碼

讓咱們接着回到類的設計中:ui

class Vaildation{
  constructor(){    
    this.isCheck = false;    
    this.vaild_item = [];    
    this.vaild;  
  }
}
複製代碼

isCheck: 驗證是否經過,類型是布爾值
vaild_item: 存放咱們傳入類中的參數
vaild: 驗證成功或失敗時返回的消息複製代碼

接下來,讓咱們來設計驗證規則rules:this

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中拿到是否成功的結果複製代碼

這篇文章僅僅是我設計這個工具時的一個思路,可能還存在一堆問題,就先拋磚引玉一下,也但願你們能多多指正,最後感謝你們的收看。

相關文章
相關標籤/搜索