ahooks解決用戶多次提交方法示例
引言
本文是深入淺出 ahooks 源碼系列文章的第四篇,這個(gè)系列的目標(biāo)主要有以下幾點(diǎn):
- 加深對(duì) React hooks 的理解。
- 學(xué)習(xí)如何抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫(kù)。
- 培養(yǎng)閱讀學(xué)習(xí)源碼的習(xí)慣,工具庫(kù)是一個(gè)對(duì)源碼閱讀不錯(cuò)的選擇。
注:本系列對(duì) ahooks 的源碼解析是基于 v3.3.13
。自己 folk 了一份源碼,主要是對(duì)源碼做了一些解讀,可見(jiàn) 詳情。
系列文章:
本文來(lái)探索一下 ahooks 的 useLockFn。
場(chǎng)景
試想一下,有這么一個(gè)場(chǎng)景,有一個(gè)表單,你可能多次提交,就很可能導(dǎo)致結(jié)果不正確。
解決這類問(wèn)題的方法有很多,比如添加 loading,在第一次點(diǎn)擊之后就無(wú)法再次點(diǎn)擊。另外一種方法就是給請(qǐng)求異步函數(shù)添加上一個(gè)靜態(tài)鎖,防止并發(fā)產(chǎn)生。這就是 ahooks 的 useLockFn 做的事情。
useLockFn
useLockFn
用于給一個(gè)異步函數(shù)增加競(jìng)態(tài)鎖,防止并發(fā)執(zhí)行。
它的源碼比較簡(jiǎn)單,如下所示:
import { useRef, useCallback } from 'react'; // 用于給一個(gè)異步函數(shù)增加競(jìng)態(tài)鎖,防止并發(fā)執(zhí)行。 function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) { // 是否現(xiàn)在處于一個(gè)鎖中 const lockRef = useRef(false); // 返回的是增加了競(jìng)態(tài)鎖的函數(shù) return useCallback( async (...args: P) => { // 判斷請(qǐng)求是否正在進(jìn)行 if (lockRef.current) return; // 請(qǐng)求中 lockRef.current = true; try { // 執(zhí)行原有請(qǐng)求 const ret = await fn(...args); // 請(qǐng)求完成,狀態(tài)鎖設(shè)置為 false lockRef.current = false; return ret; } catch (e) { // 請(qǐng)求失敗,狀態(tài)鎖設(shè)置為 false lockRef.current = false; throw e; } }, [fn], ); } export default useLockFn;
可以看到,它的入?yún)⑹钱惒胶瘮?shù),返回的是一個(gè)增加了競(jìng)態(tài)鎖的函數(shù)。通過(guò) lockRef 做一個(gè)標(biāo)識(shí)位,初始化的時(shí)候它的值為 false。當(dāng)正在請(qǐng)求,則設(shè)置為 true,從而下次再調(diào)用這個(gè)函數(shù)的時(shí)候,就直接 return,不執(zhí)行原函數(shù),從而達(dá)到加鎖的目的。
缺點(diǎn)
雖然實(shí)用,但缺點(diǎn)很明顯,我需要給每一個(gè)需要添加競(jìng)態(tài)鎖的請(qǐng)求異步函數(shù)都手動(dòng)加一遍。那有沒(méi)有比較通用和方便的方法呢?
答案是可以通過(guò) axios 自動(dòng)取消重復(fù)請(qǐng)求。
axios 自動(dòng)取消重復(fù)請(qǐng)求
axios 取消請(qǐng)求
對(duì)于原生的 XMLHttpRequest 對(duì)象發(fā)起的 HTTP 請(qǐng)求,可以調(diào)用 XMLHttpRequest 對(duì)象的 abort 方法。
那么我們項(xiàng)目中常用的 axios 呢?它其實(shí)底層也是用的 XMLHttpRequest 對(duì)象,它對(duì)外暴露取消請(qǐng)求的 API 是 CancelToken??梢允褂萌缦拢?/p>
const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.post('/user/12345', { name: 'gopal' }, { cancelToken: source.token }) source.cancel('Operation canceled by the user.'); // 取消請(qǐng)求,參數(shù)是可選的
另外一種使用的方法是調(diào)用 CancelToken 的構(gòu)造函數(shù)來(lái)創(chuàng)建 CancelToken,具體使用如下:
const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; }) }); cancel(); // 取消請(qǐng)求
如何自動(dòng)取消重復(fù)的請(qǐng)求
知道了如何取消請(qǐng)求,那怎么做到自動(dòng)取消呢?答案是通過(guò) axios 的攔截器。
- 請(qǐng)求攔截器:該類攔截器的作用是在請(qǐng)求發(fā)送前統(tǒng)一執(zhí)行某些操作,比如在請(qǐng)求頭中添加 token 相關(guān)的字段。
- 響應(yīng)攔截器:該類攔截器的作用是在接收到服務(wù)器響應(yīng)后統(tǒng)一執(zhí)行某些操作,比如發(fā)現(xiàn)響應(yīng)狀態(tài)碼為 401 時(shí),自動(dòng)跳轉(zhuǎn)到登錄頁(yè)。
具體的做法如下:
第一步,定義幾個(gè)重要的輔助函數(shù)。
generateReqKey
:用于根據(jù)當(dāng)前請(qǐng)求的信息,生成請(qǐng)求 Key。只有 key 相同才會(huì)判定為是重復(fù)請(qǐng)求。這一點(diǎn)很重要,而且可能跟具體的業(yè)務(wù)場(chǎng)景有關(guān),比如有一種請(qǐng)求,輸入框模糊搜索,用戶高頻輸入關(guān)鍵字,一次性發(fā)出多個(gè)請(qǐng)求,可能先發(fā)出的請(qǐng)求,最后才響應(yīng),導(dǎo)致實(shí)際搜索結(jié)果與預(yù)期不符。這種其實(shí)就只需要根據(jù) URL 和請(qǐng)求方法判定其為重復(fù)請(qǐng)求,然后取消之前的請(qǐng)求就可以了。
這里我認(rèn)為,如果有需要的話,可以暴露一個(gè) API 給開(kāi)發(fā)者進(jìn)行自定義重復(fù)的規(guī)則。這里我們先根據(jù)請(qǐng)求方法、url、以及參數(shù)生成唯一的 key 去做。
function generateReqKey(config) { const { method, url, params, data } = config; return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&"); }
- addPendingRequest。用于把當(dāng)前請(qǐng)求信息添加到 pendingRequest 對(duì)象中。
const pendingRequest = new Map(); function addPendingRequest(config) { const requestKey = generateReqKey(config); config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => { if (!pendingRequest.has(requestKey)) { pendingRequest.set(requestKey, cancel); } }); }
- removePendingRequest。檢查是否存在重復(fù)請(qǐng)求,若存在則取消已發(fā)的請(qǐng)求。
function removePendingRequest(config) { const requestKey = generateReqKey(config); if (pendingRequest.has(requestKey)) { const cancelToken = pendingRequest.get(requestKey); cancelToken(requestKey); pendingRequest.delete(requestKey); } }
第二步,添加請(qǐng)求攔截器。
axios.interceptors.request.use( function (config) { removePendingRequest(config); // 檢查是否存在重復(fù)請(qǐng)求,若存在則取消已發(fā)的請(qǐng)求 addPendingRequest(config); // 把當(dāng)前請(qǐng)求信息添加到pendingRequest對(duì)象中 return config; }, (error) => { return Promise.reject(error); } );
第二步,添加響應(yīng)攔截器。
axios.interceptors.response.use( (response) => { removePendingRequest(response.config); // 從pendingRequest對(duì)象中移除請(qǐng)求 return response; }, (error) => { removePendingRequest(error.config || {}); // 從pendingRequest對(duì)象中移除請(qǐng)求 if (axios.isCancel(error)) { console.log("已取消的重復(fù)請(qǐng)求:" + error.message); } else { // 添加異常處理 } return Promise.reject(error); } );
到這一步,我們就通過(guò) axios 完成了自動(dòng)取消重復(fù)請(qǐng)求的功能。
思考與總結(jié)
雖然可以通過(guò)類似 useLockFn 這樣的 hook或方法給請(qǐng)求函數(shù)添加競(jìng)態(tài)鎖的方式解決重復(fù)請(qǐng)求的問(wèn)題。但這種還是需要依賴于開(kāi)發(fā)者的習(xí)慣,如果沒(méi)有一些規(guī)則的約束,很難避免問(wèn)題。
通過(guò) axios 攔截器以及其 CancelToken 功能,我們能夠在攔截器中自動(dòng)將已發(fā)的請(qǐng)求取消,當(dāng)然假如有一些接口就是需要重復(fù)發(fā)送請(qǐng)求,可以考慮加一下白名單功能,讓請(qǐng)求不進(jìn)行取消。
以上就是ahooks解決用戶多次提交方法示例的詳細(xì)內(nèi)容,更多關(guān)于ahooks用戶多次提交的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react render的原理及觸發(fā)時(shí)機(jī)說(shuō)明
這篇文章主要介紹了react render的原理及觸發(fā)時(shí)機(jī)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02一文詳解ReactNative狀態(tài)管理redux-toolkit使用
這篇文章主要為大家介紹了ReactNative狀態(tài)管理redux-toolkit使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03React實(shí)現(xiàn)全局組件的Toast輕提示效果
這篇文章主要介紹了React實(shí)現(xiàn)全局組件的Toast輕提示效果,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09react?fiber使用的關(guān)鍵特性及執(zhí)行階段詳解
這篇文章主要為大家介紹了react?fiber使用的關(guān)鍵特性及執(zhí)行階段詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05