> 文档中心 > uni-app开发日志[2022022701]:解决因异步原因导致子组件调用父组件中uni-form表单验证事件时发生的错误及uniform、promise、async、await的同步异步使用注意点

uni-app开发日志[2022022701]:解决因异步原因导致子组件调用父组件中uni-form表单验证事件时发生的错误及uniform、promise、async、await的同步异步使用注意点

标题不知道怎么取好,有建议留言

uni-form是uniapp官方组件,其中表单验证部分使用异步方式,这样当子组件调用父组件的验证事件时,该验证事件将排在最后执行从而导致无法获得正确验证结果。解决这个问题需要结合表单验证、同步异步、promise、catch、then、async、await等内容。
注意,本文代码使用的是vue3.0,其它版本尚未测试过。

先对uni-form的部分表单验证有个了解 uni-forms表单其他方法说明

问题

一般情况下子组件是通过props的事件属性或emits方式来监听父组件事件。
下面的例子为一个发送短信验证码的子组件,发送验证码之前要先通过父组件的验证。

子组件sms-code

//子组件通过click事件触发执行send,send中首先执行sendValidate验证<input @click="send" />//props提供验证相关属性,属于事件类型sendValidate:{type:Function}//methods中执行发送send(){console.log('第一步:开始(1)');//首先执行props提供的验证事件if(!this.sendValidate()){console.log('第五步:判断后操作(2)');//这里先不返回//return;}console.log('第六步:判断结束(3)');//执行发送uni.request({...console.log('第七步:发送验证码(4)');});...console.log('第八步:结束(5)');}

父组件

//template中调用该组件<sms-code :sendValidate="validate"></sms-code>//methods中写入一个validate方法validate(){console.log('第二步:验证开始(a)');let r = false;//uni-form针对单一控件的验证规则,这里验证用户名及手机this.$refs.form.validateField(['name', 'mobile']).then((res)=>{console.log('第三步:验证中(b)');r = (!res)?false:true;});console.log('第四步:验证结束(c)');return r;}

执行后会发现,顺序不对。

第一步:开始(1)第二步:验证开始(a)第四步:验证结束(c)第五步:判断后操作(2)第六步:判断结束(3)第八步:结束(5)第三步:验证中(b)第七步:发送验证码(4

这样的话,没等判断成功就执行下去了。

分析

原因很简单,就是因为this.$refs.form.validateField()uni.request()为异步执行,所以两个异步函数就被排到了后面执行。
本文不研究uni.request(),这里只是演示同步异步在一起后的顺序。

下面这二篇文章非常重要,帮助我真正理解并解决了问题。
Promise和Async/Await用法整理
vue 表单验证由异步变更为同步

1、解除一个异步执行需要用到await标识,但只有异步函数的内部才能存在await标识,因此需要用async来使该函数异步化;
2、父组件中validate()函数内用到的validateField()函数是异步,因此需要用await标记为同步,而为了使用await,又需要将validate()函数标记上async,变为异步函数;
3、由于父组件的validate()函数是异步,因此子组件send()函数内在调用时需要用await来同步,而为了使用awaitsend()函数也需要标记上async
4、如果不标记父组件中validate()函数内的validateField()函数为await,那么validate()函数就不需要标记为async,这样子组件send()函数的asyncawait标记了也没有意义,validateField()函数依旧将被放到最后去执行,最终还是错误的顺序;
5、所以在父组件和子组件中需要各套一次asyncawait是必须和必要的。

好了,我写得太搞脑子了,也确实搞了我很久的脑子,还是看具体代码吧。

解决方法

现在将之前的代码进行改写:

子组件sms-code

//子组件通过click事件触发执行send,send中首先执行sendValidate验证<input @click="send" />//props提供验证相关属性,属于事件类型sendValidate:{type:Function}//methods中执行发送async send(){console.log('第一步:开始(1)');//首先执行props提供的验证事件if(!await this.sendValidate()){console.log('第五步:判断后操作(2)');//这里先不返回//return;}console.log('第六步:判断结束(3)');//执行发送uni.request({...console.log('第七步:发送验证码(4)');});...console.log('第八步:结束(5)');}

父组件

//template中调用该组件<sms-code :sendValidate="validate"></sms-code>//methods中写入一个validate方法async validate(){console.log('第二步:验证开始(a)');let r = false;//uni-form针对单一控件的验证规则,这里验证用户名及手机await this.$refs.form.validateField(['name', 'mobile']).then((res)=>{console.log('第三步:验证中(b)');r = (!res)?false:true;});console.log('第四步:验证结束(c)');return r;}

这样执行顺序就对了,因为uni.request()依旧为异步,所以排到了最后。

第一步:开始(1)第二步:验证开始(a)第三步:验证中(b)第四步:验证结束(c)第五步:判断后操作(2)第六步:判断结束(3)第八步:结束(5)第七步:发送验证码(4

深入挖掘

在解决这个问题时绕了很多弯路,这里写几个:

this.$refs.form.validateField()表单验证的三个部分

this.$refs.form.validateField(['name'],(res)=>{//res返回的是不符合验证规则的详细内容,如果验证成功则返回null,与then相反//参数callback可以不写}).then((res)=>{//res返回的是验证成功后该控件的值,如果验证失败则为null}).catch((res)=>{//返回错误提示,例如//[{key: "username", errorMessage: "请输入账号"},{key: "mobile", errorMessage: "请输入手机号"}]);

this.$refs.form.validateField()表单验证的catch()如果没写

系统会提示如下:

  • 警告uni-h5.es.js:13989 [Vue warn]: Unhandled error during execution of native event handler at
  • 错误Uncaught (in promise)

this.$refs.form.validateField()表单验证的then()可以分离

比如上面例子也可以这样写:

子组件sms-code

//子组件通过click事件触发执行send,send中首先执行sendValidate验证<input @click="send" />//props提供验证相关属性,属于事件类型sendValidate:{type:Function}//methods中执行发送async v(){let r = false;await this.sendValidate().then((res)=>{r = (!res)?false:true;});return r;},async send(){if(!await this.v()){return;}//执行发送uni.request({...});...}

父组件

//template中调用该组件<sms-code :sendValidate="validate"></sms-code>//methods中写入一个validate方法validate(){return this.$refs.form.validateField(['name', 'mobile']);}

本段为废话:
本方法可以减少父组件的代码,之前代码需要在父组件或页面上标记asyncawait,各有用途:
如果子组件应用较广的话建议用之前的代码,让父组件中的函数必须提供布尔返回值,这样子组件容错率高。
如果子组件使用人群不是很懂代码,那么用这里的方法就很好了,因为他们不需要考虑同步异步的问题。

看见没?知道了原理,怎么拆怎么合就得心应手了。以后遇到其它同步异步问题时,可以加上promise()再来搞。

使用promise把异步操作包裹起来,并使用then来读取其返回值

废话不多说,用uni.request()来举个例子

// 异步需要返回 Promise 对象let l =  new Promise((resolve, reject) => {uni.request({   url: 'test/load',    data: {},   method:"POST",   header : {"content-type":"application/x-www-form-urlencoded;charset=utf-8"},   success : (res)=> {if(res.data.result==='success'){resolve();}else{resolve('出错啦');//resolve中的内容由then抓取}   },   fail : (res)=> {reject(new error('系统问题')); //reject中的内容由catch获取   },   complete : (res)=> {}});});l.then(e=>{console.log(e)}).catch(err=>{console.log(err);});

异步验证规则使用方法:uni-forms表单 validateFunction 异步校验
例子中已经说明了validateFunction()中已经对thencatch进行了处理,所以直接return promise即可。

validateTrigger表单校验时机只有submit和bind

uni-forms表单 表单校验时机说明

如果有一个自定义的校验规则,比如注册账号时实时与服务器匹配账号是否已存在,那么如果用bind,遇到个人不停改动。。。
所以这时候但凡有与服务器相关的校验,则使用submit