Vue.js 学习总结(19)—— Vue3 按钮防重复点击三种方案总结

前言

我们在使用 uniapp 开发完移动端项目之后,需要对程序的性能进行优化:比如防止按钮或者 view 重复点击等。下面是我给出的3种解决方案:

方案一:状态锁

核心:通过设置变量 true/false 来控制按钮或者 view 的状态。

这种方式应该最常见。

javascript 复制代码
<template>
  <view>
  <!-- 作者:知否技术 -->
<!--1. 视图 -->
<view :class="{ 'disabled': isLoading }" @click="handleSubmit">
   提交
</view>
<!--2. 按钮 disabled -->
<uv-button :disabled="isLoading" @click="handleSubmit"> 提交 </uv-button>
<!--3. 按钮 loading -->
<uv-button :loading="isLoading" @click="handleSubmit">提交</uv-button>
 </view>
</template>

<script setup>
  import {ref} from 'vue'
  import orderApi from '@/api/order/order.js';
  const isLoading = ref(false)
  // 提交方法
  const handleSubmit = async () => {
      // 防止重复点击
      if (isLoading.value) return
      // 锁住
      isLoading.value = true
      try {
          const res = await orderApi.handleSubmit(order);
          if (res.code === 200) {
              getReturnFundList();
              uni.showToast({ title: '提交成功' });
          } else {
              uni.showToast({ title: '提交失败' });
          }
      } catch (error) {
          console.error("提交失败:", error);
      } finally {
          // 解锁
          isLoading.value = false;
      }
  }
</script>

方案二:全局自定义防重复点击指令

在项目根目录中新建 /directives/preventClick.js :

1. preventClick.js

javascript 复制代码
// 防止重复点击指令
exportconst preventClick = {
// 指令绑定到元素时执行
  mounted(el, binding) {
    // 1. 设置防抖间隔时间,默认1000毫秒
    const delay = binding.value || 1000;
    
    // 2. 创建一个标记,记录当前是否可点击
    el.isDisabledClick = false;
    
    // 3. 保存原始的点击事件处理函数
    const originalClickHandler = el.__originalClickHandler;
    
    // 4. 创建新的点击事件处理函数
    el.__preventClickHandler = (event) => {
      // 如果当前不可点击,直接拦截点击事件
      if (el.isDisabledClick) {
        event.preventDefault(); // 阻止默认行为
        event.stopPropagation(); // 阻止事件冒泡
        return;
      }
      
      // 设置为不可点击状态
      el.isDisabledClick = true;
      
      // 5. 执行原始的点击事件处理函数
      if (originalClickHandler) {
        originalClickHandler(event);
      }
      
      // 6. 设置定时器,在指定时间后恢复点击
      el.preventClickTimer = setTimeout(() => {
        // 恢复点击状态
        el.isDisabledClick = false;
        // 清除定时器引用
        el.preventClickTimer = null;
      }, delay);
    };
    
    // 7. 移除原始的点击事件,添加新的点击事件
    el.removeEventListener('click', originalClickHandler);
    el.addEventListener('click', el.__preventClickHandler);
  },

// 指令从元素解绑时执行
  unmounted(el) {
    // 8. 清除定时器
    if (el.preventClickTimer) {
      clearTimeout(el.preventClickTimer);
      el.preventClickTimer = null;
    }
    
    // 9. 移除事件监听器
    if (el.__preventClickHandler) {
      el.removeEventListener('click', el.__preventClickHandler);
      el.__preventClickHandler = null;
    }
    
    // 10. 清除自定义属性
    el.isDisabledClick = null;
    el.__originalClickHandler = null;
  }
};

2. 在 main.js 中全局注册指令

javascript 复制代码
import { createSSRApp } from'vue';
import App from'./App.vue';
// 导入指令
import { preventClick } from'@/directives/preventClick';

exportfunction createApp() {
const app = createSSRApp(App);
// 全局注册指令,指令名为 prevent-click
  app.directive('prevent-click', preventClick);
return { app };
}

3.在组件中使用指令

javascript 复制代码
<template>
  <view>
    <!-- 方式1:使用默认 1000ms 防抖 -->
    <button v-prevent-click @click="handleClick">点击</button>
    <!-- 方式2:自定义防抖时间 -->
    <button v-prevent-click="2000" @click="handleClick">点击</button>
  </view>
</template>

方案三:封装一个防重复点击的 Hook

在项目目录 hooks 新建文件: usePreventReclick.js

javascript 复制代码
import { ref } from'vue'
exportfunction usePreventReclick(delay = 1000) {
const isDisabled = ref(false)
// 执行受保护的操作 fn - 要执行的函数(建议返回 Promise)
// 返回 fn 的结果或 undefined(如果被阻止)
const execute = async (fn) => {
    if (isDisabled.value) {
      console.info('操作被阻止')
      returnundefined
    }
// 设置为 true
    isDisabled.value = true
    try {
      const result = await fn()
      return result
    } catch (error) {
      // 抛出异常
      throw error
    } finally {
      // 延迟后自动重置状态
      setTimeout(() => {
        isDisabled.value = false
      }, delay)
    }
  }
// 解除禁用
const reset = () => {
    isDisabled.value = false
  }
return {
    isDisabled,
    execute,
    reset
  }
}

在项目中使用:

javascript 复制代码
<template>
  <view class="container">
    <button 
      :disabled="isLoading" 
      :loading="isLoading"
      @click="handleSubmit"
    >
      {{ isLoading ? '正在提交中...' : '提交订单' }}
    </button>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { usePreventReclick } from '@/hooks/usePreventReclick'
const isLoading = ref(false)
// 创建防重实例:时间 1500 毫秒
const { isDisabled: isLoading, execute } = usePreventReclick(1500)

const handleSubmit = () => {
  execute(async () => {
    // 模拟后端请求
    await new Promise()
    uni.showToast({ title: '提交成功'})
  }).catch(err => {
    console.error('提交失败:', err)
    uni.showToast({ title: '提交失败'})
  })
}
</script>
相关推荐
华玥作者1 天前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_1 天前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
前端摸鱼匠1 天前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
lang201509281 天前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC1 天前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务1 天前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
嘿起屁儿整1 天前
面试点(网络层面)
前端·网络
VT.馒头1 天前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
phltxy1 天前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
Byron07071 天前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js