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是必须和必要的。

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

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

解决方法

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

子组件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

你可能感兴趣的:(前端,App,javascript,前端,vue.js,uni-app,vue)