多次重复点击,只触发一次(vue指令实现)

本文介绍了一种基于Promise的自定义指令,用于限制在表单提交和付款等场景下的频繁点击,避免网络请求影响。它通过捕捉resolve和reject来精准控制点击状态,对比了防抖和节流的不足。但需要注意适时使用reject以处理错误情况,如表单验证未通过。
摘要由CSDN通过智能技术生成

使用场景:

  1. 提交表单的时候,只有在表单成功提交之后,才能下一次提交,处于网络请求中的时候,不能够提交。

  2. 付款的时候,只能够付一次,同样处于网络请求中,不能够提交等等

优点(也算是吧):

  1. 相对于使用防抖和节流的操作,防抖和节流会受到网络请求的快慢影响,如果网络请求过慢,而且两次点击的时间超过防抖和节流预设的时间,还会触发多次的。本方式使用捕捉promise中resolve和reject的方式,更加精确在什么时候可以触发禁止点击和放开点击

  2. 相对于使用变量控制,不需要额外的声明变量,只需要使用解构过来的resolve和reject控制即可。

  3. 因为禁用和非禁用使用代码精确控制,所以适用的场景应该很多。目前在网络请求的案例上,问题都能够解决。

存在的缺点:

  1. 就是某个按钮的点击操作要执行一次时候,需要用promise包括,在promise中根据reject和resolve来控制按钮在点击之后是否还能继续点击。

  2. 如果遇到了提交表单的情况,且表单没验证通过,不能进行提交的话,也要需要使用reject,表示下次继续可以点击。

    另外表单验证只是另外的一种,确切的说,只要用户进行了点击,那么那一瞬间或者说是只要没使用reject就是不能够进行下一次点击的。如果想要继续可以点击,需要使用reject。所以在使用该指令的时候,注意reject的时机!!!示例如下:

参数解释:

参数:params , options

  1. 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>
  2. 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中的是bindunbind,在vue3中使用的是mountedunmounted

// 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); // 注册

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值