背景
實際業務開發場景中,往往存在有些大數據請求的需求,一旦請求發起加載遮罩后用戶就無法操作了,直接尬住,所以提供一個支持取消查詢的功能還是很有必要的,為了在全業務接口都能使用封裝一個hook。
?為什么要用 AbortController?
AbortController
是瀏覽器提供的原生 API,用于中止 Web 請求(如 Fetch)。你可以通過調用 abort()
來通知一個綁定了該信號(signal
)的請求停止執行。
簡單來說,它的用法是這樣的:
const controller = new AbortController();
const signal = controller.signal;fetch('/api/slow-request', { signal }).catch(err => {if (err.name === 'AbortError') {console.log('請求被中止了');}
});setTimeout(() => {controller.abort(); // 中止請求
}, 2000);
這在 Vue 中也完全適用,尤其是你使用 Axios、Fetch 或其他支持 AbortSignal 的封裝庫時。
🧠 思考:hook設計需解決的問題?
-
啟動查詢時,展示一個 loading 動畫。
-
2 秒后仍未返回結果,彈出一個“取消查詢”的提示框。
-
如果用戶點擊“取消”,則主動中止請求。
-
請求成功或被取消后,清除提示框和 loading。
這就是我們這個 Hook 要完成的全部職責。
🏃?? 實現步驟
第一步:定義中止信號 signal
我們在 Hook 中需要一個 ref 來存儲當前的 AbortSignal,方便傳給請求調用者。
const signal = ref<AbortSignal>({} as AbortSignal);
同時,我們在每次調用前重新創建一個 AbortController,保證每次請求都能獨立控制。
第二步:啟動 loading 和延遲彈窗
當用戶點擊“查詢”按鈕后,我們要立即顯示一個 ElLoading 動畫,然后 延遲 2 秒 后再彈出取消窗口。
為什么要延遲?
很多請求會在 2 秒內返回,沒必要給用戶太多打擾。我們只在“慢”的時候,才提醒用戶可以取消。
loading = ElLoading.service({lock: true,text: '',background: 'rgba(0,0,0,0.2)',
});timer = setTimeout(() => {ElMessageBox.confirm('<div class="flex flex-col gap-3 items-center"><div class="w-10 h-10 border-4 border-t-blue-500 border-gray-300 rounded-full animate-spin"></div><p>查詢中...</p></div>','提示',{dangerouslyUseHTMLString: true,customClass: 'custom-style',showClose: false,showCancelButton: false,confirmButtonText: '取消查詢',closeOnClickModal: false,closeOnPressEscape: false,},).then(() => {// 中止請求controller.abort();// 中止 后端真實請求查詢stopTrueRequest().then(() => {ElMessage.success('已取消查詢!');});});}, 2000);
這個 MessageBox 是關鍵,它展示了一個動畫+提示文字,并提供“取消查詢”的按鈕。當點擊時,我們會執行:
controller.abort();
中止請求,取消回調里可以調用真實取消查詢的接口。
第三步:清理 timer 和 loading
無論請求成功還是被取消,我們都要記得清理 timer 和 loading:
const cancelPendingAlert = () => {loading?.close();if (timer) {clearTimeout(timer);timer = null;}
};
// 卸載時也要清理
onUnmounted(() => {if (timer) clearTimeout(timer);if (loading) loading.close();
});
最終 Hook 導出結構
return {loadCancelAlert,cancelPendingAlert,signal,
};
🚀 如何在頁面中調用?
我們在業務組件中使用這個 Hook 時,可以這樣寫:
const { loadCancelAlert, cancelPendingAlert, signal } = useCancelRequest();const testCancel = () => {loadCancelAlert(); // 顯示 loading & 準備彈窗testCancelApi('', signal.value).then(() => {cancelPendingAlert();}).finally(() => {cancelPendingAlert(); // 兜底關閉ElMessageBox.close(); // 主動關閉提示框});
};
注意這里的 testCancelApi 是你封裝的接口請求函數,它需要支持接收 signal:
export function testCancelApi<T>(data: string, signal: AbortSignal) {return request<T>({url: '/api/v1/test',method: 'POST',data,signal, // ? 添加 signal 支持中斷});
}
最終效果
界面
實際請求
hook 完整代碼
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus';
import { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
import { onUnmounted, ref } from 'vue';export default function useCancelRequest() {const signal = ref<AbortSignal>({} as AbortSignal); // 終止標識let timer: ReturnType<typeof setTimeout> | null = null; // 定時器延遲彈窗加載let loading: LoadingInstance;/** 初始化取消請求彈窗 */const loadCancelAlert = () => {const controller = new AbortController(); // 請求終止器signal.value = controller.signal;loading = ElLoading.service({lock: true,text: '',background: 'rgab(0,0,0,0.2)',});timer = setTimeout(() => {ElMessageBox.confirm('<div class="flex flex-col gap-3 items-center"><div class="w-10 h-10 border-4 border-t-blue-500 border-gray-300 rounded-full animate-spin"></div><p>查詢中...</p></div>','提示',{dangerouslyUseHTMLString: true,customClass: 'custom-style',showClose: false,showCancelButton: false,confirmButtonText: '取消查詢',closeOnClickModal: false,closeOnPressEscape: false,},).then(() => {// 中止請求controller.abort();// 中止 后端真實請求查詢stopTrueRequest().then(() => {ElMessage.success('已取消查詢!');});});}, 2000);};// 請求完成時調用,取消加載取消請求彈窗const cancelPendingAlert = () => {loading?.close();if (timer) {clearTimeout(timer);timer = null;}};onUnmounted(() => {if (timer) clearTimeout(timer);if (loading) loading.close();});return {loadCancelAlert,cancelPendingAlert,signal,};
}
完結撒花🎉🎉🎉
歡迎點贊+收藏+關注😀