使用场景
- 提交表单的时候,只有在表单成功提交之后,才能下一次提交,处于网络请求中的时候,不能够提交。
- 付款的时候,只能够付一次,同样处于网络请求中,不能够提交等等
优点(也算是吧):
- 相对于使用防抖和节流的操作,防抖和节流会受到网络请求的快慢影响,如果网络请求过慢,而且两次点击的时间超过防抖和节流预设的时间,还会触发多次的。本方式使用捕捉promise中resolve和reject的方式,更加精确在什么时候可以触发禁止点击和放开点击
- 相对于使用变量控制,不需要额外的声明变量,只需要使用解构过来的resolve和reject控制即可。
- 因为禁用和非禁用使用代码精确控制,所以适用的场景应该很多。目前在网络请求的案例上,问题都能够解决。
存在的缺点:
-
可能使用解构出来的东西,你并不习惯
-
如果遇到了提交表单的情况,且表单没验证通过,不能进行提交的话,也要需要使用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:预留配置项,暂时没有用,不用传值
使用方法
-
将下面的函数放入到utils公共方法中,或者是放到一个js文件中
// 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); } }; -
在
main.js中引入上面的这个setReclickOption,并且注册。然后就可以在vue文件中使用了import {setReclickOption} from './utils/directive'; Vue.directive('clickonce', setReclickOption); -
使用
<div class="submit" v-reclick:applyRecruit=“{parans:{name:'tom',age:16}}”>申请加入</div> -
解构需要使用的参数
e:触发的事件 , 和click中触发的e相同。 reject/resolve:控制下次是否可以点击,实际上只需要resolve就可以了 。 params:传递的参数
clickResolve({resolve, reject, e, params}) { let resolveURL = '' this.$ax.get(resolveURL) .then(res => { console.log(e, params); }).catch(err => { console.log(err); reject(); }); },
实现思路
实现的思路非常简单,也就是在注册指令的时候对使用v-reclick事件的dom元素进行监听,监听他的click点击事件。并且在该dom上添加data-click的属性,初始设置为0,标记可以进行点击(data-click标记0可以点击,1不可以点击)。
-
注册的时候,初始化data-click设置为0,并且监听这个dom的click事件
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); } }; -
触发click事件,会先判断data-click是0还是1 , 是0则可以触发点击事件触发的函数
const handleClick = async(e , binding) =>{ // ... let flag = Number(e.target.getAttribute('data-click')); if (flag) { e.preventDefault(); return; } // ... } -
触发之前,判断传递的参数是否正确,并且将状态立即设置为1
const handleClick = async(e , binding) =>{ // ... 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); // ... } -
加下来触发函数,因为改函数使用promise封装,所以函数执行过程中在try...catch中可控,根据promise中的resolve/reject。如果是resolve则
data-click则为1,不可进行点击,如果是reject,则可以继续点击const handleClick = async(e , binding) =>{ // ... try { await new Promise((resolve, reject) => binding.instance[fn]({resolve, reject, e, params})); } catch (err) { console.log('点击操作失败', err); e.target.setAttribute('data-click', 0); } // ... }
指令注册代码(vue3中)
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);
}
}
// 注册指令
export const setReclickOption ={
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参数截图