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>
相关推荐
指尖跳动的光2 小时前
前端视角-如何保证系统稳定性
前端
麦麦大数据2 小时前
F064 vue+flask知识图谱在线学习系统
vue.js·flask·知识图谱·在线学习·学习系统·ai学伴·ai助学
fruge2 小时前
2025全栈技术深耕与实践:从框架融合到工程落地
前端
秋4272 小时前
tomcat与web服务器
服务器·前端·tomcat
hdsoft_huge2 小时前
Java 实现高效查询海量 geometry 及 Protobuf 序列化与天地图前端分片加载
java·前端·状态模式
MoonBit月兔2 小时前
用 MoonBit 打造的 Luna UI:日本开发者 mizchi 的 Web Components 实践
前端·数据库·mysql·ui·缓存·wasm·moonbit
程序员修心2 小时前
CSS浮动与表格布局全解析
前端·html
登山人在路上2 小时前
Vuex构建可维护的 Vue.js 状态管理
vue.js
登山人在路上3 小时前
Pinia :下一代 Vue 状态管理
vue.js