优雅的写表单校验 -- 策略模式的运用

406 阅读4分钟

假设我们正在编写一个注册的页面,在点击注册按钮之前,有如下几条校验逻辑。

  • 用户名不能为空。
  • 密码长度不能少于 6 位。
  • 手机号码必须符合格式。
<form action="http:// xxx.com/register" id="registerForm" method="post">
  <div>请输入用户名:<input type="text" name="userName" /></div>
  <div>请输入密码:<input type="text" name="password" /></div>
  <div>请输入手机号码:<input type="text" name="phoneNumber" /></div>
  <button>提交</button>
</form>

html 的部分很简单,主要想想提交里怎么写?

来,简单构思下~
来,简单构思下~
来,简单构思下~

凭直觉写的表单校验 - v1

let form = document.querySelector("#registerForm");
form.onsubmit = () => {
  if (form.userName.value === "") {
    alert("用户名不能为空");
    return false;
  }
  if (form.password.value.length < 6) {
    alert("密码不能少于6位");
    return false;
  }
  if (!/[0-9]{11,11}/.test(form.phoneNumber.value)) {
    alert("手机号码格式不正确");
    return false;
  }
};

一顿操作猛如虎,好,写完之后,看看代码有何不妥:

  • registerForm.onsubmit 函数比较庞大,包含了很多 if-else 语句,这些语句需要覆盖所有 的校验规则。
  • registerForm.onsubmit 函数缺乏弹性,如果增加了一种新的校验规则,或者想把密码的长 度校验从 6 改成 8,我们都必须深入 registerForm.onsubmit 函数的内部实现,这是违反开 放—封闭原则的。
  • 算法的复用性差,如果在程序中增加了另外一个表单,这个表单也需要进行一些类似的 校验,那我们很可能将这些校验逻辑复制得漫天遍野。

联系之前说过的策略模式,来改进代码~

改进版的表单校验 - v2

// 将算法的实现单独封装起来
var strategies = {
  isNonEmpty: function(value, errorMsg) {
    // 不为空
    if (value === "") {
      return errorMsg;
    }
  },
  // 这里把length作为参数
  minLength: function(value, length, errorMsg) {
    // 限制最小长度
    if (value.length < length) {
      return errorMsg;
    }
  },
  isMobile: function(value, errorMsg) {
    // 手机号码格式
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  }
};
// 算法的使用
let form = document.querySelector("#registerForm");
form.onsubmit = function() {
  var errorMsg = "用户名不能为空";
  if ((strategies.isNonEmpty(registerForm.userName.value), errorMsg)) {
    alert(errorMsg);
    return false;
  }
  var errorMsg = "密码长度不能少于6 位";
  if ((strategies.minLength(registerForm.password.value), 6, errorMsg)) {
    alert(errorMsg);
    return false;
  }
  var errorMsg = "手机号码格式不正确";
  if ((strategies.isMobile(registerForm.phoneNumber.value), errorMsg)) {
    alert(errorMsg);
    return false;
  }
  // 这里开始ajax请求。。。
};

写完之后,我屮艸芔茻!!!这好像和第一版也没什么大的区别。看起来一点也不优雅!!!

但这里提供了关键性思路,提交部分明显就是重复,很容易想到列表循环,这里将其抽象成 validator 的类。

也就是 validator 类提供 add 和 start,,add 负责将每种校验方式放进一个数组,start 负责遍历数组,一旦有返回值直接返回且终止返回。

。。。咳咳,有点难度,可以试试写写。

初见美好 - v3

// var strategies = 、、、
class Validator {
  constructor() {
    this.cache = [];
  }
  add(dom, rule, msg) {
    // 把单个验证表单的fn存下来
    let [strategy, length] = rule.split(":");
    let params = length ? [dom.value, length, msg] : [dom.value, msg];
    console.log(params);
    let fn = () => {
      return strategies[strategy](...params);
    };
    this.cache.push(fn);
  }
  start() {
    for (var i = 0; i < this.cache.length; i++) {
      let errMsg = this.cache[i]();
      if (errMsg) {
        return errMsg;
      }
    }
  }
}
// 应用
let form = document.querySelector("#registerForm");
function validateData() {
  let validator = new Validator();
  validator.add(form.userName, "isNonEmpty", "用户名不能为空");
  validator.add(form.password, "minLength:6", "密码不能少于6位");
  validator.add(form.phoneNumber, "isMobile", "手机号码格式不正确");
  // 需要加新的校验规则,直接添加这里就好
  let errMsg = validator.start();
  return errMsg;
}
// submit里面就很优雅了
form.onsubmit = () => {
  let errMsg = validateData();
  if (errMsg) {
    alert(errMsg);
    return false;
  }
};

写完这些之后,很赞啦!
但。。。还有点点缺点,比如用户名想同时验证其为空和长度的话,需要单个列出,显然又累赘了
接下来试着将其改成validator.add(form.userName, {isNonEmpty:'用户名不能为空','minLength:6':'用户名长度不能少于6位'})

validator.add(form.userName, "isNonEmpty", "用户名不能为空");
validator.add(form.userName, "minLength:6", "用户名长度不能少于6位");

相知相识 - v4

// var strategies = 。。。;
class Validator {
  constructor() {
    this.cache = [];
  }
  // 没事原先的add提取出来
  _handleRule(dom, rule, msg) {
    // 把单个验证表单的fn存下来
    let [strategy, length] = rule.split(":");
    let params = length ? [dom.value, length, msg] : [dom.value, msg];
    console.log(params);
    let fn = () => {
      return strategies[strategy](...params);
    };
    this.cache.push(fn);
  }
  add(dom, rules) {
    rules.forEach(item => {
      for (let rule in item) {
        let msg = item[rule];
        // 遍历的时候,直接使用就好
        this._handleRule(dom, rule, msg);
      }
    });
  }
  start() {
    for (var i = 0; i < this.cache.length; i++) {
      let errMsg = this.cache[i]();
      if (errMsg) {
        return errMsg;
      }
    }
  }
}
// 应用
let form = document.querySelector("#registerForm");

function validateData() {
  let validator = new Validator();
  validator.add(form.userName, [
    { isNonEmpty: "用户名不能为空" },
    { "minLength:6": "用户名不能少于6位" }
  ]);
  validator.add(form.password, [{ "minLength:6": "密码不能少于6位" }]);
  validator.add(form.phoneNumber, [{ isMobile: "手机号码格式不正确" }]);
  console.log(validator);
  let errMsg = validator.start();
  return errMsg;
}
form.onsubmit = () => {
  let errMsg = validateData();
  if (errMsg) {
    alert(errMsg);
    return false;
  }
};

完结,下次看表单验证的一些插件可能就能加深理解了!

引用

这里的案例是《JavaScript的设计模式与开发实践》里面的,写的非常好,强烈安利!!!

对了之前写了个策略模式的入门