最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3防重復(fù)點擊指令clickOnce使用方法

 更新時間:2026年03月21日 11:20:13   作者:碼途進化論  
使用Vue 框架開發(fā)項目中,遇到個問題,就是在用戶提交數(shù)據(jù)時,如果連續(xù)多次點擊按鈕,會觸發(fā)多次,導(dǎo)致數(shù)據(jù)異常,這篇文章主要介紹了Vue3防重復(fù)點擊指令clickOnce使用的相關(guān)資料,需要的朋友可以參考下

一、問題背景

在實際的 Web 應(yīng)用開發(fā)中,我們經(jīng)常會遇到以下問題:

  1. 用戶快速多次點擊提交按鈕:導(dǎo)致重復(fù)提交表單,產(chǎn)生多條相同數(shù)據(jù)
  2. 異步請求未完成時再次點擊:可能導(dǎo)致數(shù)據(jù)不一致或服務(wù)器壓力增大
  3. 用戶體驗不佳:沒有明確的加載狀態(tài)反饋,用戶不知道操作是否正在進行

這些問題在以下場景中尤為常見:

  • 表單提交(注冊、登錄、創(chuàng)建訂單等)
  • 數(shù)據(jù)保存操作
  • 文件上傳
  • 支付操作
  • API 調(diào)用

二、解決方案

clickOnce 指令通過以下機制解決上述問題:

1.節(jié)流機制

使用 @vueuse/coreuseThrottleFn,在 1.5 秒內(nèi)只允許執(zhí)行一次點擊操作。

2.按鈕禁用

點擊后立即禁用按鈕,防止用戶再次點擊。

3.視覺反饋

自動添加 Element Plus 的 Loading 圖標,讓用戶明確知道操作正在進行中。

4.智能恢復(fù)

  • 如果綁定的函數(shù)返回 Promise(異步操作),則在 Promise 完成后自動恢復(fù)按鈕狀態(tài)
  • 如果是同步操作,則立即恢復(fù)

三、核心特性

? 自動防重復(fù)點擊:1.5秒節(jié)流時間

? 自動 Loading 狀態(tài):無需手動管理 loading 變量

? 支持異步操作:自動檢測 Promise 并在完成后恢復(fù)

? 優(yōu)雅的清理機制:組件卸載時自動清理事件監(jiān)聽

? 類型安全:完整的 TypeScript 支持

四、技術(shù)實現(xiàn)

關(guān)鍵技術(shù)點

  1. Vue 3 自定義指令:使用 Directive 類型定義
  2. VueUse 節(jié)流useThrottleFn 提供穩(wěn)定的節(jié)流功能
  3. 動態(tài)組件渲染:使用 createVNoderender 動態(tài)創(chuàng)建 Loading 圖標
  4. Promise 檢測:自動識別異步操作并在完成后恢復(fù)狀態(tài)

工作流程

用戶點擊按鈕
    ↓
節(jié)流檢查(1.5秒內(nèi)只執(zhí)行一次)
    ↓
禁用按鈕 + 添加 Loading 圖標
    ↓
執(zhí)行綁定的函數(shù)
    ↓
檢測返回值是否為 Promise
    ↓
Promise 完成后(或同步函數(shù)執(zhí)行完)
    ↓
移除 Loading + 恢復(fù)按鈕狀態(tài)

五、使用方法

1. 注冊指令

// main.ts
import clickOnce from '@/directives/clickOnce'

app.directive('click-once', clickOnce)

2. 在組件中使用

<template>
  <!-- 異步操作示例 -->
  <el-button 
    type="primary" 
    v-click-once="handleSubmit">
    提交表單
  </el-button>

  <!-- 帶參數(shù)的異步操作 -->
  <el-button 
    type="success" 
    v-click-once="() => handleSave(formData)">
    保存數(shù)據(jù)
  </el-button>
</template>

<script setup lang="ts">
const handleSubmit = async () => {
  // 模擬 API 調(diào)用
  await api.submitForm(formData)
  ElMessage.success('提交成功')
}

const handleSave = async (data: any) => {
  await api.saveData(data)
  ElMessage.success('保存成功')
}
</script>

六、優(yōu)勢對比

傳統(tǒng)方式

<template>
  <el-button 
    type="primary" 
    :loading="loading"
    :disabled="loading"
    @click="handleSubmit">
    提交
  </el-button>
</template>

<script setup lang="ts">
const loading = ref(false)

const handleSubmit = async () => {
  if (loading.value) return
  
  loading.value = true
  try {
    await api.submit()
  } finally {
    loading.value = false
  }
}
</script>

問題

  • 需要手動管理 loading 狀態(tài)
  • 每個按鈕都要寫重復(fù)代碼
  • 容易遺漏 finally 清理邏輯

使用 clickOnce 指令

<template>
  <el-button 
    type="primary" 
    v-click-once="handleSubmit">
    提交
  </el-button>
</template>

<script setup lang="ts">
const handleSubmit = async () => {
  await api.submit()
}
</script>

優(yōu)勢

  • 代碼簡潔,無需管理狀態(tài)
  • 自動處理 loading 和禁用
  • 統(tǒng)一的用戶體驗

七、注意事項

  1. 僅用于異步操作:該指令主要為異步操作設(shè)計,同步操作會立即恢復(fù)
  2. 綁定函數(shù)必須返回 Promise:對于異步操作,確保函數(shù)返回 Promise
  3. 節(jié)流時間固定:當前節(jié)流時間為 1.5 秒,可根據(jù)需求調(diào)整 THROTTLE_TIME 常量
  4. 依賴 Element Plus:使用了 Element Plus 的 Loading 圖標和樣式

八、適用場景

? 適合使用

  • 表單提交按鈕
  • 數(shù)據(jù)保存按鈕
  • 文件上傳按鈕
  • API 調(diào)用按鈕
  • 支付確認按鈕

? 不適合使用

  • 普通導(dǎo)航按鈕
  • 切換/開關(guān)按鈕
  • 需要快速連續(xù)點擊的場景(如計數(shù)器)

九、指令源碼

import type { Directive } from 'vue'
import { createVNode, render } from 'vue'
import { useThrottleFn } from '@vueuse/core'
import { Loading } from '@element-plus/icons-vue'

const THROTTLE_TIME = 1500

const clickOnce: Directive<HTMLButtonElement, () => Promise<unknown> | void> = {
  mounted(el, binding) {
    const handleClick = useThrottleFn(
      () => {
        // 如果元素已禁用,直接返回(雙重保險)
        if (el.disabled) return

        // 禁用按鈕
        el.disabled = true
        // 添加 loading 狀態(tài)
        el.classList.add('is-loading')

        // 創(chuàng)建 loading 圖標容器
        const loadingIconContainer = document.createElement('i')
        loadingIconContainer.className = 'el-icon is-loading'

        // 使用 Vue 的 createVNode 和 render 來渲染 Loading 組件
        const vnode = createVNode(Loading)
        render(vnode, loadingIconContainer)

        // 將 loading 圖標插入到按鈕開頭
        el.insertBefore(loadingIconContainer, el.firstChild)

        // 將 loading 圖標存儲到元素上,以便后續(xù)移除
        ;(el as any)._loadingIcon = loadingIconContainer
        ;(el as any)._loadingVNode = vnode

        // 執(zhí)行綁定的函數(shù)(應(yīng)返回 Promise 或普通函數(shù))
        const result = binding.value?.()

        const removeLoading = () => {
          el.disabled = false
          // 移除 loading 狀態(tài)
          el.classList.remove('is-loading')
          const icon = (el as any)._loadingIcon
          if (icon && icon.parentNode === el) {
            // 卸載 Vue 組件
            render(null, icon)
            el.removeChild(icon)
            delete (el as any)._loadingIcon
            delete (el as any)._loadingVNode
          }
        }

        // 如果返回的是 Promise,則在完成時恢復(fù);否則立即恢復(fù)
        if (result instanceof Promise) {
          result.finally(removeLoading)
        } else {
          // 非異步操作,立即恢復(fù)(或根據(jù)需求決定是否恢復(fù))
          // 通常建議只用于異步操作,所以這里也可以不處理,或給出警告
          removeLoading()
        }
      },
      THROTTLE_TIME,
    )

    // 將 throttled 函數(shù)存儲到元素上,以便在 unmount 時移除
    ;(el as any)._throttledClick = handleClick
    el.addEventListener('click', handleClick)
  },

  beforeUnmount(el) {
    const handleClick = (el as any)._throttledClick
    if (handleClick) {
      el.removeEventListener('click', handleClick)
      // 取消可能還在等待的 throttle
      handleClick.cancel?.()
      delete (el as any)._throttledClick
    }
  },
}

export default clickOnce

十、總結(jié)

clickOnce 指令通過封裝防重復(fù)點擊邏輯,提供了一個開箱即用的解決方案,讓開發(fā)者可以專注于業(yè)務(wù)邏輯,而不用擔心重復(fù)點擊的問題。它結(jié)合了節(jié)流、狀態(tài)管理和視覺反饋,為用戶提供了更好的交互體驗。

到此這篇關(guān)于Vue3防重復(fù)點擊指令clickOnce使用方法的文章就介紹到這了,更多相關(guān)Vue3防重復(fù)點擊指令clickOnce內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 10個Vue3中的編程實用技巧分享

    10個Vue3中的編程實用技巧分享

    這篇文章主要為大家詳細介紹了10個Vue3中的編程實用技巧,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-03-03
  • vue中使用定義好的變量設(shè)置css樣式詳解

    vue中使用定義好的變量設(shè)置css樣式詳解

    vue項目中我們可以通過行內(nèi)樣式進行動態(tài)修改樣式,下面這篇文章主要給大家介紹了關(guān)于vue中如何使用定義好的變量設(shè)置css樣式的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-03-03
  • vue如何在多個不同服務(wù)器下訪問不同地址

    vue如何在多個不同服務(wù)器下訪問不同地址

    這篇文章主要介紹了vue如何在多個不同服務(wù)器下訪問不同地址,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue父子組件屬性傳遞實現(xiàn)方法詳解

    Vue父子組件屬性傳遞實現(xiàn)方法詳解

    這篇文章主要介紹了Vue父子組件屬性傳遞實現(xiàn)方法,我們主要從案例出發(fā),用Vue3的寫法寫父子組件之間的屬性傳遞,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-02-02
  • vue與vue-i18n結(jié)合實現(xiàn)后臺數(shù)據(jù)的多語言切換方法

    vue與vue-i18n結(jié)合實現(xiàn)后臺數(shù)據(jù)的多語言切換方法

    下面小編就為大家分享一篇vue與vue-i18n結(jié)合實現(xiàn)后臺數(shù)據(jù)的多語言切換方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • vue.js element-ui tree樹形控件改iview的方法

    vue.js element-ui tree樹形控件改iview的方法

    這篇文章主要介紹了vue.js element-ui tree樹形控件改iview的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • vue 表單之通過v-model綁定單選按鈕radio

    vue 表單之通過v-model綁定單選按鈕radio

    這篇文章主要介紹了vue 表單之v-model綁定單選按鈕radio的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-05-05
  • Vue如何利用flex布局實現(xiàn)TV端城市列表功能

    Vue如何利用flex布局實現(xiàn)TV端城市列表功能

    用vue開發(fā)了三四個組件了,都是H5的,現(xiàn)在來看看PC是如何玩轉(zhuǎn)組件的,下面這篇文章主要給大家介紹了關(guān)于Vue如何利用flex布局實現(xiàn)TV端城市列表功能的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • vue{{}}拼接字符串和換行符方式

    vue{{}}拼接字符串和換行符方式

    這篇文章主要介紹了vue{{}}拼接字符串和換行符方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 解決vue頁面DOM操作不生效的問題

    解決vue頁面DOM操作不生效的問題

    下面小編就為大家分享一篇解決vue頁面DOM操作不生效的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03

最新評論

河间市| 道真| 南丰县| 上高县| 天全县| 印江| 抚顺市| 平昌县| 南涧| 六枝特区| 安溪县| 新泰市| 仲巴县| 改则县| 贵阳市| 八宿县| 屏东市| 阜南县| 额尔古纳市| 蓬莱市| 张家界市| 夏邑县| 曲水县| 南宁市| 邓州市| 正蓝旗| 秀山| 象山县| 鄂尔多斯市| 辉县市| 岱山县| 新竹市| 柳江县| 岳西县| 竹北市| 商水县| 广河县| 凤翔县| 开化县| 自治县| 开江县|