标题不知道怎么取好,有建议留言
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
来同步,而为了使用await
,send()
函数也需要标记上async
;
4、如果不标记父组件中validate()
函数内的validateField()
函数为await
,那么validate()
函数就不需要标记为async
,这样子组件send()
函数的async
和await
标记了也没有意义,validateField()
函数依旧将被放到最后去执行,最终还是错误的顺序;
5、所以在父组件和子组件中需要各套一次async
和await
是必须和必要的。
好了,我写得太搞脑子了,也确实搞了我很久的脑子,还是看具体代码吧。
现在将之前的代码进行改写:
子组件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(['name'],(res)=>{
//res返回的是不符合验证规则的详细内容,如果验证成功则返回null,与then相反
//参数callback可以不写
}).then((res)=>{
//res返回的是验证成功后该控件的值,如果验证失败则为null
}).catch((res)=>{
//返回错误提示,例如
//[{key: "username", errorMessage: "请输入账号"},{key: "mobile", errorMessage: "请输入手机号"}]
});
catch()
如果没写系统会提示如下:
uni-h5.es.js:13989 [Vue warn]: Unhandled error during execution of native event handler at
Uncaught (in promise)
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']);
}
本段为废话:
本方法可以减少父组件的代码,之前代码需要在父组件或页面上标记async
或await
,各有用途:
如果子组件应用较广的话建议用之前的代码,让父组件中的函数必须提供布尔返回值,这样子组件容错率高。
如果子组件使用人群不是很懂代码,那么用这里的方法就很好了,因为他们不需要考虑同步异步的问题。
看见没?知道了原理,怎么拆怎么合就得心应手了。以后遇到其它同步异步问题时,可以加上promise()
再来搞。
废话不多说,用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()
中已经对then
和catch
进行了处理,所以直接return promise
即可。
uni-forms表单 表单校验时机说明
如果有一个自定义的校验规则,比如注册账号时实时与服务器匹配账号是否已存在,那么如果用bind
,遇到个人不停改动。。。
所以这时候但凡有与服务器相关的校验,则使用submit