策略模式实现轻便灵活的表单校验

220 阅读3分钟

考虑到接手的一些老项目有很多表单录入功能,其中涉及到大量的表单校验需求。而项目中用到的表单校验方式虽然五花八门各显神通,但都不够高效和灵活,维护起来也比较困难。 故而着手实现一套轻便又灵活的表单校验方法。在开始用策略模式实现表单校验功能前,我们先来了解一下,什么是策略模式。

策略模式简介

策略模式指的是定义一系列的算法(也可以是一系列的业务规则),把它们一个个封装起来,将变化的部分和不变的部分隔开。目的是将算法的使用和算法的实现分离开。一个基于策略模式的程序至少由两部分组成:一是策略类,二是环境类。

策略模式应用

假定现在要实现一个用户登录功能:用户点击登录按钮,需先验证用户输入的数据是否合规,然后再进行登录。其中需要被校验的字段有:

  • name:必填
  • email:非必填,注册名+@+某网站地址
  • password:必填,大小写字母+特殊字符+数字,长度8-32位
一、先看项目中原本的两种登录实现
1、简单粗暴挨个校验:

<button @click="handleClick">登录</button>

handleClick() {
  var { name, email, password } = baseObj; // baseObj为被校验对象
  
  if (!name) {
    console.log("请填写用户名");
    return;
  }
  if (!password) {
    console.log("请填写密码");
    return;
  }
  if (!regularDic.emailReg.test(email)) {
    console.log("邮箱格式不正确");
    return;
  }
  if (!regularDic.passwordReg.test(password)) {
    console.log("密码格式不正确");
    return;
  }
  login()
}


// 正则表达式
var regularDic = {
  emailReg: /^([0-9A-Za-z\-_\.]+)@([0-9A-Za-z\-_]+\.[a-z]{2,3}(\.[a-z]{2})?)$/,
  passwordReg: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![!@#$%./*()_+^&]+$)[0-9A-Za-z!@#$%./*()_+^&]{6,20}$/,
}  
  
2、巧思分类校验:

<button @click="handleClick">登录</button>

handleClick() {
  var { name, email, password } = baseObj; // baseObj为被校验对象
  /* 非空校验 */
  var checkName = {
    name: "用户名",
    password: "密码",
  };
  for (var k in baseObj) {
    if (!baseObj[k]) {
      for (var j in checkName) {
        if (k == j) {
          console.log(checkName[j] + "不能为空")
          return;
        }
      }
    }
  }
  /* 格式校验 */
  // 校验格式函数
  var checkRule = function (type, value) {
    return regularDic[`${type}Reg`].test(value);
  };
  // 正则表达式
  var regularDic = {
    emailReg: /^([0-9A-Za-z\-_\.]+)@([0-9A-Za-z\-_]+\.[a-z]{2,3}(\.[a-z]{2})?)$/,
    passwordReg: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![!@#$%./*()_+^&]+$)[0-9A-Za-z!@#$%./*()_+^&]{6,20}$/,
  }  
  var checkFormat = {
    email: { name: "邮箱", type: "email" },
    password: { name: "密码", type: "password" },
  };
  for (let k in checkFormat) {
    if (baseObj[k]) {
      if (!this.checkRule(checkFormat[k].type, baseObj[k])) {
        console.log(checkFormat[k].name + "格式不正确");
        return;
      }
    }
  }
  login()
}

可以看出,无论哪种验证方式都存在几个明显的缺点:

  1. 不够轻便:由于函数中包含了很多if-else语句,函数很容易变得过于庞大。
  2. 不够灵活:每当有增、删、改校验规则的需求时都需要深入改动函数。
  3. 复用性差:每新增一个表单就需要编写类似的代码。

二、接下来用策略模式重构表单校验

1.制定策略

为每个需要被校验的字段制定校验规则,可知一个校验规则即为一个策略。

/* 这是一组策略,即:可自由组合搭配替换的一系列的业务规则 */
let rules = {
  name: { required: true, message: "用户名必填" },
  email: { required: false, type: "email", message: "邮箱格式不正确" },
  password: { required: true, message: "密码必填", validator: validatePassword }
}

/* 扩展部分:自定义校验函数 */
let validatePassword = (rule, value, callback) => {
  if (!regularDic.passwordReg.test(value)) {
    callback("密码格式不正确");
  }
};

/* 一系列正则表达式:用来判断值是否符合格式 */
var regularDic = {
  emailReg: /^([0-9A-Za-z\-_\.]+)@([0-9A-Za-z\-_]+\.[a-z]{2,3}(\.[a-z]{2})?)$/,
  passwordReg: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![!@#$%./*()_+^&]+$)[0-9A-Za-z!@#$%./*()_+^&]{6,20}$/,
}

接下来编写校验函数,根据以上策略对数据进行校验。

2.编写校验函数

主要实现以下三项功能:

  • 必填校验
  • 数据类型校验
  • 自定义规则校验
let regRules = function (rule, data) {
    let reg = regularDic[rule.type + 'Reg']
    return reg.test(data)
}
let validate = function (rules, datas) {
  return new Promise((resolve, reject) => {
    try {
      let valid = null;
      Object.entries(rules).forEach(([key, rule]) => {
        let value = datas[key];
        if (Object.prototype.toString.call(datas[key]) === "[object String]") {
          value = datas[key].replace(/(^\s*)|(\s*$)/g, "")
        }
        // 该字段的校验规则为对象
        if (Object.prototype.toString.call(rule) === "[object Object]") {
          // 当前字段是否为空
          let isEmpty = value === "" || value == null;
          // 是否必填
          let disRequiredAbled = rule.required === true && isEmpty
          // 数据类型校验
          let disTypeAbled = !isEmpty && rule.type && !regRules(rule, value)
          // 自定义规则是否通过
          let disCustomRuleAbled = false;
          if (rule.validator) {
            rule.validator(rule, value, message => {
              if (message) {
                disCustomRuleAbled = true;
                rule = Object.assign({}, { message: message })
              }
            })
          }
          if (disRequiredAbled || disTypeAbled || disCustomRuleAbled) {
            let message = rule.message
            if (!valid) {
              valid = { ...rule, message: message }
            }
          }
        }
        // 该字段校验规则为数组 处理逻辑类似
        // if (Object.prototype.toString.call(rule) === '[array Object]') { 

        // }
      });
      resolve(valid);
    } catch (e) {
      reject(e)
    }
  });
}
3.校验数据
<button @click="handleClick">登录</button>

handleClick() {
  var { name, email, password } = baseObj; // baseObj为被校验对象
  
  this.validate(rules, baseObj).then((valid) => {
    if (!valid) {
      // 校验通过
      login()
    } else {
      // 校验不通过
      console.log(valid.message)
    }
  });
} 

策略模式优缺点

优点:
1、策略模式可以有效避免多重条件选择语句;
2、策略模式提供了对开放-封闭原则的支持,易于扩展和切换;
3、减少复制和粘贴,提高开发效率。
缺点:
会增加许多策略类和策略对象。