使用场景:
-
提交表单的时候,只有在表单成功提交之后,才能下一次提交,处于网络请求中的时候,不能够提交。
-
付款的时候,只能够付一次,同样处于网络请求中,不能够提交等等
优点(也算是吧):
-
相对于使用防抖和节流的操作,防抖和节流会受到网络请求的快慢影响,如果网络请求过慢,而且两次点击的时间超过防抖和节流预设的时间,还会触发多次的。本方式使用捕捉promise中resolve和reject的方式,更加精确在什么时候可以触发禁止点击和放开点击
-
相对于使用变量控制,不需要额外的声明变量,只需要使用解构过来的resolve和reject控制即可。
-
因为禁用和非禁用使用代码精确控制,所以适用的场景应该很多。目前在网络请求的案例上,问题都能够解决。
存在的缺点:
-
就是某个按钮的点击操作要执行一次时候,需要用promise包括,在promise中根据reject和resolve来控制按钮在点击之后是否还能继续点击。
-
如果遇到了提交表单的情况,且表单没验证通过,不能进行提交的话,也要需要使用reject,表示下次继续可以点击。
另外表单验证只是另外的一种,确切的说,只要用户进行了点击,那么那一瞬间或者说是只要没使用reject就是不能够进行下一次点击的。如果想要继续可以点击,需要使用reject。所以在使用该指令的时候,注意reject的时机!!!示例如下:
参数解释:
参数:params , options
params:传入的参数,任意类型
<div class="submit" v-reclick:applyRecruit=“{parans:111}”>申请加入</div> <div class="submit" v-reclick:applyRecruit=“{parans:'aaa'}”>申请加入</div> <div class="submit" v-reclick:applyRecruit=“{parans:{name:'tom',age:16}}”>申请加入</div>options:预留配置项,暂时没有用,不用传值
原理:
原理很简单,使用promise封装,目的是为了能够在函数中准确的捕捉到reject和resolve的位置,然后做出是否点击可控。
try {
await new Promise((resolve, reject) => binding.instance[fn]({resolve, reject, e, params}));
} catch (err) {
console.log('点击操作失败', err);
e.target.setAttribute('data-click', 0);
}
示例:
该示例只提供reject的情况,如果使用resolve的情况,则是下次不可点击了。resolve可以用点击之后直接进行跳转,或者是关闭某种弹框,具体场景可以,自行调控。
目前提供的这个示例是,点击之后,继续留在该页面
<div class="submit" v-reclick:applyRecruit>申请加入</div>
applyRecruit({reject}) {
let me = this;
let url = "...";
if (!this.recruit_tel) {
Toast('请输入您的手机号码');
// 校验没通过,设置可以继续点击
reject();
return false;
}
if (!validMobileFormat(this.recruit_tel)) {
Toast('请输入正确的手机号码');
// 校验没通过,设置可以继续点击
reject();
return false;
}
let params = {
type: 1
};
params = {...params, ...getEncryptionData({recruit_tel: me.recruit_tel})};
if (this.tel) {
params.tel = this.tel;
params = {...params, ...getEncryptionData({tel: this.tel})};
params.device_id = this.device_id;
}
me.$axios
.post(url, params)
.then(res => {
if (res.data.code === 2000) {
me.recruit_tel = '';
}
Toast(res.data.msg);
})
.catch(err => {
Toast('网络异常,请稍后再试');
})
.finally(() => {
reject();
});
},
指令注册代码(vue3中)
const app = createApp(App)
const handleClick = async(e , binding) =>{
let target = binding.value || {}
let { params = null, options = {} } = target
let fn = binding.arg;
let flag = Number(e.target.getAttribute('data-click'));
if (flag) {
e.preventDefault();
return;
}
if (Object.prototype.toString.call(options) !== '[object Object]') {
throw new Error('options必须是一个对象')
}
if (typeof binding.instance[fn] !== 'function') {
throw new Error('v-clickounce绑定的必须是一个函数')
}
// 标识该元素进行点击过了
e.target.setAttribute('data-click', 1);
try {
await new Promise((resolve, reject) => binding.instance[fn]({resolve, reject, e, params}));
} catch (err) {
console.log('点击操作失败', err);
e.target.setAttribute('data-click', 0);
}
}
// 注册指令
app.directive('reclick', {
mounted: function (el, binding) {
el.setAttribute('data-click', 0);
el.addEventListener('click', e=> handleClick(e , binding))
},
unmounted(el){
el.removeEventListener('click' ,handleClick )
}
})
指令注册代码(vue2中)
vue2中没有bind的第二个参数中没有instance属性(组件的实例),需要到第三个参数中去拿组件虚拟dom中的方法。
然后是注册指令时的钩子函数不同,vue2中的是bind
和unbind
,在vue3中使用的是mounted
和unmounted
// h5-better-examples/utils/directive.js
const handleClick = async (e, binding, vnode) => {
let target = binding.value || {};
let {params = null, options = {}} = target;
let flag = Number(e.target.getAttribute('data-click'));
if (flag) {
e.preventDefault();
return;
}
let fn = binding.arg;
if (Object.prototype.toString.call(options) !== '[object Object]') {
throw new Error('options必须是一个对象');
}
if (typeof vnode.context[fn] !== 'function') {
throw new Error('v-clickounce绑定的必须是一个函数');
}
// 标识该元素进行点击过了
e.target.setAttribute('data-click', 1);
try {
await new Promise((resolve, reject) => vnode.context[fn]({resolve, reject, e, params}));
} catch (err) {
console.log(err);
// 因出现了catch的情况,说明本次异步请求出错了,所以应该能够再次进行点击
e.target.setAttribute('data-click', 0);
}
};
/**
* 参数是由vue2的注册指令时候传过来的参数,如有需要,vue文档中查看属性对应的值https://cn.vuejs.org/v2/guide/custom-directive.html
* el:点击的节点
* binding
* vnode:Vue 编译生成的虚拟节点。
*/
export const setReclickOption = {
inserted: function(el, binding, vnode) {
// 初始化,将data-click置为0,用于标识该元素没有点击过
el.setAttribute('data-click', 0);
el.addEventListener('click', e => handleClick(e, binding, vnode));
},
unbind(el) {
el.removeEventListener('click', handleClick);
}
};
bingding参数截图
vnode参数截图
// h5-better-examples/main.js
Vue.directive('clickonce', options); // 注册