一种 iView Form 表单验证始终为 false 的情况

问题

公司项目使用的 iView 库,发现一个业务功能代码进行表单验证时出现验证结果始终为 false 不通过的情况,代码如下,返回的始终为 false

1
2
3
4
5
6
7
8
9
10
11
validateUserForm() {
let _valid = false;
this.$refs["formUser"].validate(valid => {
if (!valid) {
this.$Message.error("请完善表单信息");
} else {
_valid = true;
}
});
return _valid;
}

3.4.x 版本工作正常,3.5.x 版本不正常。

解决过程

首先导致问题的原因基本能确定问题出在回调当中,就是回调在异步等情况进入下一个事件循环或者任务队列中,导致当前任务变量并未更新。

于是对比了两个版本的 Form.vue validate 方法并无差异(3.4.2 Form.vue validate3.5.1 Form.vue validate),除了 validate 用到了 async-validator 第三方依赖。

所以把关注重点放在了 Release v3.5.1 上的一个更新,让表单验证 message 支持设置为 function 的特性这个改动更新了 async-validator 依赖,见:Issue #6085Commit ff9617ff

怀疑是因为更新后的 async-validator v1.12.2 相对于原 iView 依赖的 v1.10.0 版本,在 validate 方法中将回调安排到了新的队列任务中导致的,但是经过对比两个版本的差异,并未发现问题,看起来和更新了 async-validator 依赖关系不大。

之后经过一通查找,最终确定问题出在 https://github.com/iview/iview/pull/6361 这里,对应 3.5.4 版本:https://github.com/iview/iview/releases/tag/3.5.4,更新后的 FormItem 组件在 validate 方法用到了 Vue nextTick 在任务队列中产生了新的任务,也就是说回调会在队列中的下一次事件循环中执行,而不是当前的。

回到最开始的问题,其实知道了原因答案自然也出来了,这里提供两种解决方式:

1、合理规范使用回调

1
2
3
4
5
6
7
8
9
validateUserForm() {
this.$refs["formUser"].validate(valid => {
if (valid) {
// do something
} else {
this.$Message.error("请完善表单信息");
}
})
}

2、使用异步

1
2
3
4
5
6
7
8
async validateUserForm() {
const _valid = await this.$refs["formUser"].validate()
if (valid) {
// do something
} else {
this.$Message.error("请完善表单信息");
}
}

这里更推荐面向现代化开发的异步方式,得益于 ES8 的特性,异步代码不用再需要曾经那复杂难以理解的回调地狱,代码更加简单易读,团队协作也能够更加高效。结合前端 Babel、ESLint、Webpack 等大量优秀的前端生态能够让开发者节省大量组织代码、处理浏览器兼容、代码查错等基础工作,让企业和开发者更加专注于业务实现和业务创新当中。

总结

  • 还需进一步深入,加深关于 JS 和 Vue 的同步、异步之间的差异和理解,在代码实践中多尝试和留意。

  • 养成良好的编码习惯,最佳实践是:在注册回调之后需要使用变量时,不要在同步代码中的回调内修改外部变量,不论回调是属于你开发的 API 还是第三方库 API,你无法确定回调何时会因为业务或者需求调整变更为异步等情况导致回调被注册到下一个事件循环或任务队列中。