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

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

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

一、問題背景

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

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

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

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

二、解決方案

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

1.節(jié)流機(jī)制

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

2.按鈕禁用

點(diǎn)擊后立即禁用按鈕,防止用戶再次點(diǎn)擊。

3.視覺反饋

自動添加 Element Plus 的 Loading 圖標(biāo),讓用戶明確知道操作正在進(jìn)行中。

4.智能恢復(fù)

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

三、核心特性

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

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

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

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

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

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

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

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

工作流程

用戶點(diǎn)擊按鈕
    ↓
節(jié)流檢查(1.5秒內(nèi)只執(zhí)行一次)
    ↓
禁用按鈕 + 添加 Loading 圖標(biāo)
    ↓
執(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)一的用戶體驗(yàn)

七、注意事項(xiàng)

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

八、適用場景

? 適合使用

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

? 不適合使用

  • 普通導(dǎo)航按鈕
  • 切換/開關(guān)按鈕
  • 需要快速連續(xù)點(diǎn)擊的場景(如計(jì)數(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(
      () => {
        // 如果元素已禁用,直接返回(雙重保險(xiǎn))
        if (el.disabled) return

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

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

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

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

        // 將 loading 圖標(biāo)存儲到元素上,以便后續(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ù)點(diǎn)擊邏輯,提供了一個開箱即用的解決方案,讓開發(fā)者可以專注于業(yè)務(wù)邏輯,而不用擔(dān)心重復(fù)點(diǎn)擊的問題。它結(jié)合了節(jié)流、狀態(tài)管理和視覺反饋,為用戶提供了更好的交互體驗(yàn)。

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

相關(guān)文章

  • 10個Vue3中的編程實(shí)用技巧分享

    10個Vue3中的編程實(shí)用技巧分享

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

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

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

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

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

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

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

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

    下面小編就為大家分享一篇vue與vue-i18n結(jié)合實(shí)現(xiàn)后臺數(shù)據(jù)的多語言切換方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    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的實(shí)例代碼,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-05-05
  • Vue如何利用flex布局實(shí)現(xiàn)TV端城市列表功能

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

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

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

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

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

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

最新評論

曲水县| 申扎县| 裕民县| 庆安县| 北碚区| 南城县| 青铜峡市| 济南市| 龙岩市| 金堂县| 伊宁市| 淮南市| 西平县| 乐山市| 松原市| 永城市| 达孜县| 于都县| 巴彦淖尔市| 上犹县| 额济纳旗| 连平县| 临猗县| 芦山县| 镇雄县| 岳西县| 上林县| 平阳县| 库车县| 浪卡子县| 仁寿县| 永吉县| 隆化县| 竹溪县| 长寿区| 明溪县| 射阳县| 宾川县| 新源县| 航空| 东源县|