Vue封裝一個(gè)自動(dòng)防重復(fù)提交的Hook
每次提交表單都要寫 loading = true、disabled = true、.finally(() => loading = false)?
你不是在寫業(yè)務(wù),你是在重復(fù)造輪子。
在日常開發(fā)中,我們無數(shù)次面對(duì)這樣的場(chǎng)景:
- 用戶點(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)
| 特性 | 說明 |
|---|---|
| 閉包鎖 | isLocked 為 true 時(shí),直接 return,不執(zhí)行原函數(shù) |
| 自動(dòng) finally 解鎖 | 即使接口報(bào)錯(cuò)、用戶中斷,也不會(huì)卡死 |
| 泛型支持 | 完美透傳參數(shù)和返回值類型 |
| 無副作用 | 不依賴全局狀態(tài),每個(gè)調(diào)用獨(dú)立隔離 |
使用場(chǎng)景全覆蓋
場(chǎng)景 1:表單提交
const [submitForm, submitting] = useSubmitLock(api.createPost);
場(chǎng)景 2:發(fā)送驗(yàn)證碼
const [sendCode, sending] = useSubmitLock(phoneApi.sendSmsCode);
// 按鈕文案可結(jié)合倒計(jì)時(shí):{sending ? '發(fā)送中...' : '獲取驗(yàn)證碼'}
場(chǎng)景 3:刪除確認(rèn)操作
const [confirmDelete, deleting] = useSubmitLock(api.deleteUser); // 防止用戶狂點(diǎn)“確定”導(dǎo)致多次刪除
場(chǎng)景 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 適用于“提交即不可逆”的場(chǎng)景。如果是上傳、下載等可取消任務(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)文章
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
Vue3實(shí)現(xiàn)富文本內(nèi)容導(dǎo)出為Word文檔
這篇文章主要為大家詳細(xì)介紹了Vue3如何通過純前端方案將富文本內(nèi)容直接導(dǎo)出為符合中文排版規(guī)范的 Word 文檔,有需要的小伙伴可以參考下2025-03-03
小白教程|一小時(shí)上手最流行的前端框架vue(推薦)
這篇文章主要介紹了前端框架vue,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
基于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ù)分組)
文章介紹了如何使用樹結(jié)構(gòu)實(shí)現(xiàn)表格數(shù)據(jù)分組,并提供了官方文檔的鏈接,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-11-11

