Form 表单拒绝写嵌套的 validate 校验

772 阅读2分钟

表单大家可能都很熟悉了,但是不知道大家在表单提交校验时有没有遇到一些困扰,最近在项目中学习到一种好用的校验方式,给大家分享一下。

Element-UI 为例,我们平常写表单验证可能是这样的,校验通过就向后端发送请求,校验失败就执行失败逻辑。

submitForm(formName) {
  this.$refs[formName].validate(async (valid) => {
    if (valid) {
      try {
        // 提交表单逻辑
      } catch (e) {
        // 上报报错逻辑
        errorReport(e);
      }
    } else {
      console.log("error submit!!");
      return false;
    }
  });
},

这段代码存在着这么几点问题:

  • 嵌套太深,一般我们写代码都会避免超过 3 层嵌套,讲究扁平化,好维护,这里到了第 3 层嵌套才开始主要逻辑,难免会产生4、5乃至更多层嵌套。
  • 这段代码太冗长记不住,一般只能去老代码中复制,然后再把其中不要的逻辑删掉,费心费力。
  • 表单提交很常见,分散在各个逻辑中不便于统一处理, 比如我想添加一个统一的报错处理逻辑或者是错误上报,分散在各处不好处理,处理起来也容易遗漏

我们可以把与业务无关的校验代码抽离封装出来,比如这一段:

this.$refs[formName].validate((valid) => {
  if (valid) {
    //...	
  } else {
    return false;
  }
});

对于开发人员来说,这一段毫无意义,可以将其扁平化,使用 Promise 封装成统一的方法,如果校验通过了,就执行 resolve(),接着执行后面的业务逻辑, 校验失败就执行 reject(),走到 catch 的逻辑。

// main.js
Vue.prototype.$validate = function (refName) {
  return new Promise((resolve, reject) => {
    this.$refs[refName].validate(async (valid) => {
      if (valid) {
        resolve(true);
      } else {
        reject(false);
      }
    });
  });
};

// 使用
async submitForm(formName) {
  try {
    await this.$validate(formName);
    // 向后端发送请求的逻辑
    console.log("submit success");
  } catch (e) {
    console.log("error");
  }
},

将一个回调地狱封装成一段 async/await代码,不仅使用起来方便了很多,而且代码的层次变得更加清晰。

通过给 Vue 实例的原型上添加 $validate 方法,就可以在各个组件中访问到 $validate 方法。

在 JavaScript 中一个原型的方法会获得该实例的上下文。也就是说它们可以使用 this 访问数据、计算属性、方法或其它任何定义在实例上的东西

具体可以看官网中的这段解释 -- 原型方法的上下文。同时,表单的校验也可以封装成一个装饰器,有兴趣的同学可以看看大佬的这篇文章 -- [Vue项目] ES6 装饰器在实战中的应用

既然我们已经将表单校验封装成了一个统一的方法,那我们可以继续往里面做一点小优化,比如说可以添加统一的报错处理逻辑,这里我们添加一个能提升用户体验的小功能。

在表单报错的时候,如果表单太长了,我们经常需要往上翻才能知道哪一项填写的不对,我们可以在表单校验失败的时候,自动滚动到报错的那一项,用户就会觉得,这个系统做的真不戳。

Vue.prototype.$validate = function (refName) {
  return new Promise((resolve, reject) => {
    this.$refs[refName].validate((valid) => {
      if (valid) {
        resolve(true);
      } else {
        this.$nextTick(() => {
          const errorList = document.getElementsByClassName(
            "el-form-item__error"
          );
          errorList.length > 0 &&
            errorList[0].scrollIntoView({
              block: "center",
              behavior: "smooth"
            });
          reject(false);
        });
      }
    });
  });
};

在校验失败的时候,在输入框下面都会有一些报错信息,他们都有一个共同的类名 el-form-item__error,有这个就好定位了。我们可以通过 getElementsByClassName方法获取到类名带有 el-form-item__error的一系列元素,再通过 scrollIntoView方法将其中的第一个 DOM 元素滚动到页面中间。

但是在实际的实验过程中,发现点击第一次校验失败的时候,并未滚动到第一个报错项,第二次校验才会滚动,将errorList打印出来可以发现,第一次校验的时候errorList只是一个空数组。所以我们需要给他添加$nextTick,来保证一定能获取到el-form-item__error类名。

这样一个完整的校验就写好了,实际用起来真的嘎嘎好用,强烈推荐这种写法,可以避免很多麻烦事。