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

Vue封裝一個(gè)自動(dòng)防重復(fù)提交的Hook

 更新時(shí)間:2026年03月25日 09:15:13   作者:前端Hardy  
在日常開發(fā)中,我們需要無數(shù)次面對(duì)防止用戶重復(fù)提交的需求,這篇文章主要為大家詳細(xì)介紹了如何通過Vue自定義封裝一個(gè)HOOK,實(shí)現(xiàn)自動(dòng)防重復(fù)提交功能,有需要的小伙伴可以了解下

每次提交表單都要寫 loading = true、disabled = true、.finally(() => loading = false)?

你不是在寫業(yè)務(wù),你是在重復(fù)造輪子。

在日常開發(fā)中,我們無數(shù)次面對(duì)這樣的場景:

  • 用戶點(diǎn)擊“提交訂單”
  • 點(diǎn)擊“發(fā)送驗(yàn)證碼”
  • 點(diǎn)擊“保存設(shè)置”

而為了防止重復(fù)點(diǎn)擊,你不得不:

  • 定義一個(gè) loading 狀態(tài);
  • 在點(diǎn)擊時(shí)設(shè)為 true;
  • 禁用按鈕;
  • 發(fā)起請(qǐng)求;
  • 成功或失敗后,再設(shè)回 false。

一段邏輯,復(fù)制粘貼十次。

更糟的是——一旦忘記寫 .finally,按鈕就永遠(yuǎn)禁用;一旦并發(fā)請(qǐng)求沒處理好,照樣重復(fù)提交。

今天,我們就用 一個(gè)自定義 Hook,徹底終結(jié)這種體力勞動(dòng)。

手動(dòng)管理 loading 的三大痛點(diǎn)

1.代碼冗余

const [submitting, setSubmitting] = useState(false);

const handleSubmit = async () => {
  if (submitting) return;
  setSubmitting(true);
  try {
    await submitForm();
  } finally {
    setSubmitting(false); // 忘記這行?按鈕就廢了
  }
};

每個(gè)按鈕都要寫一遍,毫無意義。

2.無法天然防重

即使你寫了 if (submitting) return,如果用戶快速雙擊,在 setSubmitting(true) 異步更新前,兩次點(diǎn)擊仍可能觸發(fā)兩次請(qǐng)求。

3.狀態(tài)分散,難以維護(hù)

多個(gè)按鈕?多個(gè)表單?每個(gè)都要獨(dú)立管理狀態(tài),邏輯割裂。

解法:封裝一個(gè)useSubmitLockHook

我們要實(shí)現(xiàn)的效果:

const [handleSubmit, isSubmitting] = useSubmitLock(async (formData) => {
  await api.submitOrder(formData);
  message.success('下單成功!');
});

return (
  <button disabled={isSubmitting} onClick={() => handleSubmit(data)}>
    {isSubmitting ? '提交中...' : '立即下單'}
  </button>
);

一行調(diào)用,自動(dòng)加鎖、自動(dòng)解鎖、自動(dòng)防重、自動(dòng)透傳參數(shù)!

實(shí)現(xiàn)原理:Promise 鎖 + 狀態(tài)同步

// React + TypeScript 版本(JS 可輕松轉(zhuǎn)寫)
import { useState, useCallback } from 'react';
type AsyncFunction<T extends any[], R> = (...args: T) => Promise<R>;
export const useSubmitLock = <T extends any[], R>(
  asyncFn: AsyncFunction<T, R>
) => {
  const [isLocked, setIsLocked] = useState(false);
  const wrappedFn = useCallback(
    async (...args: T): Promise<R | undefined> => {
      if (isLocked) {
        console.warn('操作正在進(jìn)行中,請(qǐng)勿重復(fù)提交');
        return; // 直接攔截,不執(zhí)行函數(shù)
      }
      setIsLocked(true);
      try {
        const result = await asyncFn(...args);
        return result;
      } finally {
        setIsLocked(false); // 無論成功失敗,一定解鎖
      }
    },
    [isLocked, asyncFn]
  );
  return [wrappedFn, isLocked] as const;
};

關(guān)鍵設(shè)計(jì)亮點(diǎn)

特性說明
閉包鎖isLockedtrue 時(shí),直接 return,不執(zhí)行原函數(shù)
自動(dòng) finally 解鎖即使接口報(bào)錯(cuò)、用戶中斷,也不會(huì)卡死
泛型支持完美透傳參數(shù)和返回值類型
無副作用不依賴全局狀態(tài),每個(gè)調(diào)用獨(dú)立隔離

使用場景全覆蓋

場景 1:表單提交

const [submitForm, submitting] = useSubmitLock(api.createPost);

場景 2:發(fā)送驗(yàn)證碼

const [sendCode, sending] = useSubmitLock(phoneApi.sendSmsCode);
// 按鈕文案可結(jié)合倒計(jì)時(shí):{sending ? '發(fā)送中...' : '獲取驗(yàn)證碼'}

場景 3:刪除確認(rèn)操作

const [confirmDelete, deleting] = useSubmitLock(api.deleteUser);
// 防止用戶狂點(diǎn)“確定”導(dǎo)致多次刪除

場景 4:組合多個(gè)異步操作

const [handlePay, paying] = useSubmitLock(async (orderId) => {
  await api.createPayment(orderId);
  await trackEvent('pay_clicked');
  window.location.href = '/payment';
});

注意事項(xiàng) & 進(jìn)階建議

1.不要用于需要“取消”的操作

此 Hook 適用于“提交即不可逆”的場景。如果是上傳、下載等可取消任務(wù),應(yīng)使用 AbortController。

2.與防重 Token 不沖突

useSubmitLock前端體驗(yàn)層防護(hù),后端仍需配合 Token 或冪等設(shè)計(jì)做最終校驗(yàn)。

3.Vue 用戶怎么辦?

同樣可封裝為 Composable:

// Vue 3 + Composition API
import { ref } from 'vue';

export function useSubmitLock(asyncFn) {
  const isLocked = ref(false);
  
  const wrappedFn = async (...args) => {
    if (isLocked.value) return;
    isLocked.value = true;
    try {
      return await asyncFn(...args);
    } finally {
      isLocked.value = false;
    }
  };

  return { execute: wrappedFn, isLocked };
}

使用:

const { execute: submit, isLocked } = useSubmitLock(api.submit);

更進(jìn)一步:自動(dòng)綁定到按鈕?

你可以再封裝一個(gè) <SubmitButton> 組件:

const SubmitButton = ({ onClick, children, ...props }) => {
  const [handler, loading] = useSubmitLock(onClick);
  return (
    <button
      disabled={loading}
      onClick={handler}
      {...props}
    >
      {loading ? '處理中...' : children}
    </button>
  );
};

// 使用
<SubmitButton onClick={submitOrder}>提交訂單</SubmitButton>

從此,防重提交,零成本集成。

結(jié)語

優(yōu)秀的工程師,不是寫更多代碼,而是讓重復(fù)的事不再發(fā)生。

一個(gè)小小的 useSubmitLock,背后是對(duì)用戶體驗(yàn)的尊重,對(duì)代碼潔癖的堅(jiān)持,更是對(duì)“DRY 原則”的踐行。

下次當(dāng)你又要寫第 101 次 loading = true 時(shí),停下來問問自己:“這事,能不能一次解決?”

把這個(gè) Hook 加到你的工具庫里,團(tuán)隊(duì)效率提升 10%。

到此這篇關(guān)于Vue封裝一個(gè)自動(dòng)防重復(fù)提交的Hook的文章就介紹到這了,更多相關(guān)Vue防重復(fù)提交內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue3使用svg圖標(biāo)的多種方式總結(jié)

    vue3使用svg圖標(biāo)的多種方式總結(jié)

    svg圖片在項(xiàng)目中使用的非常廣泛,下面這篇文章主要給大家介紹了關(guān)于vue3使用svg圖標(biāo)的多種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • Vue結(jié)合openlayers按照經(jīng)緯度坐標(biāo)實(shí)現(xiàn)錨地標(biāo)記及繪制多邊形區(qū)域

    Vue結(jié)合openlayers按照經(jīng)緯度坐標(biāo)實(shí)現(xiàn)錨地標(biāo)記及繪制多邊形區(qū)域

    OpenLayers是一個(gè)用于開發(fā)WebGIS客戶端的JavaScript包,最初基于BSD許可發(fā)行。OpenLayers是一個(gè)開源的項(xiàng)目,其設(shè)計(jì)之意是為互聯(lián)網(wǎng)客戶端提供強(qiáng)大的地圖展示功能,包括地圖數(shù)據(jù)顯示與相關(guān)操作,并具有靈活的擴(kuò)展機(jī)制
    2022-09-09
  • Vue-cli打包后部署到子目錄下的路徑問題說明

    Vue-cli打包后部署到子目錄下的路徑問題說明

    這篇文章主要介紹了Vue-cli打包后部署到子目錄下的路徑問題說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Vue3實(shí)現(xiàn)富文本內(nèi)容導(dǎo)出為Word文檔

    Vue3實(shí)現(xiàn)富文本內(nèi)容導(dǎo)出為Word文檔

    這篇文章主要為大家詳細(xì)介紹了Vue3如何通過純前端方案將富文本內(nèi)容直接導(dǎo)出為符合中文排版規(guī)范的 Word 文檔,有需要的小伙伴可以參考下
    2025-03-03
  • 小白教程|一小時(shí)上手最流行的前端框架vue(推薦)

    小白教程|一小時(shí)上手最流行的前端框架vue(推薦)

    這篇文章主要介紹了前端框架vue,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 一文帶你上手vue3中的pinia

    一文帶你上手vue3中的pinia

    這篇文章主要以vue3+vite+ts舉例,為大家詳細(xì)介紹了vue3中pinia的安裝與使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-09-09
  • vue中使用svg畫路徑圖的詳細(xì)介紹

    vue中使用svg畫路徑圖的詳細(xì)介紹

    這篇文章主要介紹了vue中使用svg畫路徑圖的相關(guān)知識(shí),在這大家需要注意svg中不能使用html標(biāo)簽,例如div,img等,因此在svg中一般使用image標(biāo)簽放置圖片,text圖片放置文本內(nèi)容,詳細(xì)代碼跟隨小編一起看看吧
    2022-04-04
  • 基于Vue2實(shí)現(xiàn)動(dòng)態(tài)折扣表格

    基于Vue2實(shí)現(xiàn)動(dòng)態(tài)折扣表格

    這篇文章主要為大家詳細(xì)介紹了如何基于Vue2實(shí)現(xiàn)動(dòng)態(tài)折扣表格,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-01-01
  • vxe-table?實(shí)現(xiàn)表格數(shù)據(jù)分組功能(按指定字段數(shù)據(jù)分組)

    vxe-table?實(shí)現(xiàn)表格數(shù)據(jù)分組功能(按指定字段數(shù)據(jù)分組)

    文章介紹了如何使用樹結(jié)構(gòu)實(shí)現(xiàn)表格數(shù)據(jù)分組,并提供了官方文檔的鏈接,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • vue3下watch的使用方法示例

    vue3下watch的使用方法示例

    vue3中的watch是一個(gè)組合式的API使用時(shí)需要引入,下面這篇文章主要給大家介紹了關(guān)于vue3下watch使用的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03

最新評(píng)論

平山县| 南昌县| 松溪县| 米林县| 安阳县| 遂川县| 钦州市| 五莲县| 札达县| 湄潭县| 乌苏市| 宁南县| 封开县| 浦东新区| 湖南省| 嘉黎县| 溆浦县| 屯昌县| 房产| 广宗县| 延长县| 文登市| 闻喜县| 合江县| 灌南县| 徐汇区| 米脂县| 沭阳县| 民勤县| 云浮市| 丰原市| 乳山市| 揭阳市| 巴里| 通道| 江阴市| 宾阳县| 定结县| 迁西县| 奈曼旗| 常州市|