學(xué)習(xí)ahooks useRequest并實(shí)現(xiàn)手寫
前言
最近業(yè)務(wù)沒有之前緊張了,也是消失了一段時間,也總結(jié)了一些之前業(yè)務(wù)上的問題。
和同事溝通也是發(fā)現(xiàn)普通的async
+ await
+ 封裝api
在復(fù)雜業(yè)務(wù)場景下針對于請求的業(yè)務(wù)邏輯比較多,也是推薦我去學(xué)習(xí)一波ahooks,由于問題起源于請求,因此作者也是直接從 useRequest
開始看起。
附ahooks useRequest
鏈接:
實(shí)現(xiàn)
話不多說,手寫直接開始,參考幾個比較常用的 useRequest
能力來一個個實(shí)現(xiàn)吧。
基礎(chǔ)版(雛形)
先上代碼:
useRequest.ts
interface UseRequestOptionsProps { /* * 請求參數(shù) */ initialData?: object; /* * 請求成功回調(diào) */ onSuccess?: (res: any) => void; } const useRequest = ( requestFn: ( initialData?: object | string | [], ) => Promise<SetStateAction<any>>, options: UseRequestOptionsProps, ) => { const [data, setData] = useState<SetStateAction<any>>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const { initialData, onSuccess } = options; useEffect(() => { setLoading(true); setError(null); setData(null); request(); }, [requestFn]); // useRequest業(yè)務(wù)邏輯 const request = async () => { try { const res = await requestFn(initialData); setData(res); // 請求成功響應(yīng)回調(diào) onSuccess && onSuccess(res); } catch (err) { err && setError(JSON.stringify(err)); } finally { setLoading(false); } }; return { data, loading, error }; }; export default useRequest;
使用
const { data, loading, error } = useRequest( queryCompensatoryOrderSituation, { initialData: { compensatoryId, } onSuccess: (res) => { console.log('success request!', res); }, }, );
useRequest
對于請求函數(shù)的寫法并無過多要求,只要是一個異步function
且返回一個promise
對象,即可傳入useRequest
的第一個參數(shù)中,而第二個參數(shù)則是一系列的可選配置項(xiàng),雛形版本我們暫時只支持onSuccess
。
手動觸發(fā)
代碼改造后:
useRequest.ts
interface UseRequestOptionsProps { /* * 手動開啟 */ manual?: boolean; /* * 請求參數(shù) */ initialData?: object; /* * 請求成功回調(diào) */ onSuccess?: (res: any) => void; } const useRequest = ( requestFn: ( initialData?: object | string | [], ) => Promise<SetStateAction<any>>, options: UseRequestOptionsProps, ) => { const [data, setData] = useState<SetStateAction<any>>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const { manual, initialData, onSuccess } = options; useEffect(() => { setLoading(true); setError(null); setData(null); !manual && request(); }, [manual]); // useRequest業(yè)務(wù)邏輯 const request = async () => { try { const res = await requestFn(initialData); setData(res); // 請求成功響應(yīng)回調(diào) onSuccess && onSuccess(res); } catch (err) { err && setError(JSON.stringify(err)); } finally { setLoading(false); } }; return { data, loading, error, request }; }; export default useRequest;
使用
const { data, loading, error, request } = useRequest( queryCompensatoryOrderSituation, { manual: true, initialData: { compensatoryId, }, onSuccess: (res) => { console.log('success request!', res); }, }, ); request();
手動執(zhí)行的邏輯主要是根據(jù)manual
參數(shù)砍掉useRequest mount
階段的渲染請求,把執(zhí)行請求的能力暴露出去,在頁面中去手動調(diào)用request()
來觸發(fā)。
輪詢與手動取消
代碼改造后:
useRequest.ts
interface UseRequestOptionsProps { /* * 手動開啟 */ manual?: boolean; /* * 請求參數(shù) */ initialData?: object; /* * 輪詢 */ pollingInterval?: number | null; /* * 請求成功回調(diào) */ onSuccess?: (res: any) => void; } const useRequest = ( requestFn: ( initialData?: object | string | [], ) => Promise<SetStateAction<any>>, options: UseRequestOptionsProps, ) => { const [data, setData] = useState<SetStateAction<any>>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const status = useRef<boolean>(false); const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null); const { manual, initialData, pollingInterval, onSuccess } = options; useEffect(() => { setLoading(true); setError(null); setData(null); !manual && request(); }, [manual]); // useRequest業(yè)務(wù)邏輯 const request = async () => { try { !status.current && (status.current = true); if (pollingInterval && status.current) { pollingIntervalTimer.current = setTimeout(() => { status.current && request(); }, pollingInterval); } const res = await requestFn(initialData); setData(res); // 請求成功響應(yīng)回調(diào) onSuccess && onSuccess(res); } catch (err) { err && setError(JSON.stringify(err)); } finally { setLoading(false); } }; return { data, loading, error, request, cancel }; }; // 取消 const cancel = () => { if (pollingIntervalTimer.current) { clearTimeout(pollingIntervalTimer.current); pollingIntervalTimer.current = null; status.current && (status.current = false); } }; export default useRequest;
使用
const { data, loading, error, request, cancel } = useRequest( queryCompensatoryOrderSituation, { manual: true, initialData: { compensatoryId, }, pollingInterval: 1000, onSuccess: (res) => { console.log('success request!', res); }, }, ); request(); ... // 輪詢到理想數(shù)據(jù)后 cancel();
輪詢的支持在hook中主要用到了timer setTimeout
的遞歸思路,同時給出一個status
狀態(tài)值判斷是否在輪詢中,當(dāng)調(diào)用端執(zhí)行cancel()
,status
則為false
;當(dāng)輪詢開始,則status
為true
。
而cancel()
的能力 主要也是取消了timer
的遞歸請求邏輯,并且輪詢的業(yè)務(wù)場景和manual: true
配合很多。
依賴請求(串型請求)
代碼改造后:
useRequest.ts
interface UseRequestOptionsProps { /* * 手動開啟 */ manual?: boolean; /* * 請求參數(shù) */ initialData?: object; /* * 輪詢 */ pollingInterval?: number | null; /* * 準(zhǔn)備,用于依賴請求 */ ready?: boolean; /* * 請求成功回調(diào) */ onSuccess?: (res: any) => void; } const useRequest = ( requestFn: ( initialData?: object | string | [], ) => Promise<SetStateAction<any>>, options: UseRequestOptionsProps, ) => { const [data, setData] = useState<SetStateAction<any>>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const status = useRef<boolean>(false); const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null); const { manual, initialData, pollingInterval, ready = true, onSuccess, } = options; useEffect(() => { setLoading(true); setError(null); setData(null); !manual && ready && request(); }, [manual, ready]); // useRequest業(yè)務(wù)邏輯 const request = async () => { try { !status.current && (status.current = true); if (pollingInterval && status.current) { pollingIntervalTimer.current = setTimeout(() => { status.current && request(); }, pollingInterval); } const res = await requestFn(initialData); setData(res); // 請求成功響應(yīng)回調(diào) onSuccess && onSuccess(res); } catch (err) { err && setError(JSON.stringify(err)); } finally { setLoading(false); } }; return { data, loading, error, request, cancel }; }; // 取消 const cancel = () => { if (pollingIntervalTimer.current) { clearTimeout(pollingIntervalTimer.current); pollingIntervalTimer.current = null; status.current && (status.current = false); } }; export default useRequest;
使用
const [mountLoading, setMountLoading] = useState<boolean>(false); useEffect(() => { setMountLoading(true); }, [2000]) const { data, loading, error, request, cancel } = useRequest( queryCompensatoryOrderSituation, { initialData: { compensatoryId, }, pollingInterval: 1000, ready: mountLoading, onSuccess: (res) => { console.log('success request!', res); }, }, );
依賴請求的思路就是在hook
中加入一個ready
字段,也是在基于manual
一層的限制后又加了一層,來判斷是否在hook
加載時是否做默認(rèn)請求,而當(dāng)option
中的ready
更新(為true)時,hook自動更新從而發(fā)起請求。
常用于頁面中A請求完成后執(zhí)行B請求,B請求的ready
字段依賴于A請求的data
/loading
字段。
防抖與節(jié)流
防抖和節(jié)流的實(shí)現(xiàn)比較簡單,依賴于lodash
庫,包裝了一下request
函數(shù)的請求內(nèi)容。
代碼如下:
useRequest.ts
interface UseRequestOptionsProps { /* * 手動開啟 */ manual?: boolean; /* * 請求參數(shù) */ initialData?: object; /* * 輪詢 */ pollingInterval?: number | null; /* * 準(zhǔn)備,用于依賴請求 */ ready?: boolean; /* * 防抖 */ debounceInterval?: number; /* * 節(jié)流 */ throttleInterval?: number; /* * 請求成功回調(diào) */ onSuccess?: (res: any) => void; } const useRequest = ( requestFn: ( initialData?: object | string | [], ) => Promise<SetStateAction<any>>, options: UseRequestOptionsProps, ) => { const [data, setData] = useState<SetStateAction<any>>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const status = useRef<boolean>(false); const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null); const { manual, initialData, pollingInterval, ready = true, debounceInterval, throttleInterval onSuccess, } = options; useEffect(() => { setLoading(true); setError(null); setData(null); !manual && ready && request(); }, [manual, ready]); // 請求 const request = () => { if (debounceInterval) { lodash.debounce(requestDoing, debounceInterval)(); } else if (throttleInterval) { lodash.throttle(requestDoing, throttleInterval)(); } else { requestDoing(); } }; // useRequest業(yè)務(wù)邏輯 const requestDoing = async () => { try { !status.current && (status.current = true); if (pollingInterval && status.current) { pollingIntervalTimer.current = setTimeout(() => { status.current && request(); }, pollingInterval); } const res = await requestFn(initialData); setData(res); // 請求成功響應(yīng)回調(diào) onSuccess && onSuccess(res); } catch (err) { err && setError(JSON.stringify(err)); } finally { setLoading(false); } }; // 取消 const cancel = () => { if (pollingIntervalTimer.current) { clearTimeout(pollingIntervalTimer.current); pollingIntervalTimer.current = null; status.current && (status.current = false); } }; export default useRequest;
使用
const { data, loading, error, request, cancel } = useRequest( queryCompensatoryOrderSituation, { manual: true, initialData: { compensatoryId, }, debounceInterval: 1000, // 防抖 throttleInterval: 1000, // 節(jié)流 onSuccess: (res) => { console.log('success request!', res); }, }, ); for(let i = 0; i < 10000; i++) { request(); }
在hook
中,通過lodash.debounce/lodash.throttle
來包裝request
函數(shù)主體,通過option
中的判斷來執(zhí)行對應(yīng)的包裝體函數(shù)。
緩存與依賴更新
改造后的代碼(最終代碼)如下:
useRequest.ts
import { useState, useEffect, useRef, SetStateAction, useCallback, } from 'react'; import lodash from 'lodash'; interface UseRequestOptionsProps { /* * 手動開啟 */ manual?: boolean; /* * 請求參數(shù) */ initialData?: object; /* * 輪詢 */ pollingInterval?: number | null; /* * 準(zhǔn)備,用于依賴請求 */ ready?: boolean; /* * 防抖 */ debounceInterval?: number; /* * 節(jié)流 */ throttleInterval?: number; /* * 延遲loading為true的時間 */ loadingDelay?: number; /* * 依賴 */ refreshDeps?: any[]; /* * 請求成功回調(diào) */ onSuccess?: (res: any) => void; } const useRequest = ( requestFn: ( initialData?: object | string | [], ) => Promise<SetStateAction<any>>, options: UseRequestOptionsProps, ) => { const [data, setData] = useState<SetStateAction<any>>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const status = useRef<boolean>(false); const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null); const { manual, initialData, pollingInterval, ready = true, debounceInterval, throttleInterval, loadingDelay, refreshDeps, onSuccess, } = options; useEffect(() => { if (loadingDelay) { setTimeout(() => { status && setLoading(true); }, loadingDelay); } setError(null); setData(null); // 手動觸發(fā)request !manual && ready && request(); }, [manual, ready, ...(Array.isArray(refreshDeps) ? refreshDeps : [])]); // 請求 const request = () => { if (debounceInterval) { lodash.debounce(requestDoing, debounceInterval)(); } else if (throttleInterval) { lodash.throttle(requestDoing, throttleInterval)(); } else { requestDoing(); } }; // useRequest業(yè)務(wù)邏輯 const requestDoing = async () => { try { !status.current && (status.current = true); if (pollingInterval && status.current) { pollingIntervalTimer.current = setTimeout(() => { status.current && request(); }, pollingInterval); } const res = await requestFn(initialData); setData(res); // 請求成功響應(yīng)回調(diào) onSuccess && onSuccess(res); } catch (err) { err && setError(JSON.stringify(err)); } finally { setLoading(false); } }; // 取消 const cancel = () => { if (pollingIntervalTimer.current) { clearTimeout(pollingIntervalTimer.current); pollingIntervalTimer.current = null; status.current && (status.current = false); } }; // 緩存 const cachedFetchData = useCallback(() => data, [data]); return { data, loading, error, request, cancel, cachedFetchData }; }; export default useRequest;
使用
const [mountLoading, setMountLoading] = useState<boolean>(false); const [updateLoading, setUpdateLoading] = useState<boolean>(false); setTimeout(() => { setMountLoading(true); }, 1000); setTimeout(() => { setUpdateLoading(true); }, 2000); const { data, loading, error, request, cancel, cachedFetchData } = useRequest( queryCompensatoryOrderSituation, { manual: true, initialData: { compensatoryId, }, debounceInterval: 1000, // 防抖 throttleInterval: 1000, // 節(jié)流 refreshDeps: [mountLoading, updateLoading], onSuccess: (res) => { console.log('success request!', res); }, }, );
緩存的主體思路是在useRequest
中拿到第一次數(shù)據(jù)后通過useCallback
來透出data
依賴來保存,同時向外暴露一個cachedFetchData
來過渡data
從null
到請求到接口數(shù)據(jù)的過程。
依賴更新的思路則是在頁面中給useRequest
一系列依賴狀態(tài)一并加入在hook
的請求副作用中,監(jiān)聽到頁面中依賴改變,則重新請求,具體實(shí)現(xiàn)則是refreshDeps
參數(shù)。
結(jié)尾
花了一上午時間,一個簡易版本的useRequest
實(shí)現(xiàn)了,也是通過實(shí)現(xiàn)學(xué)習(xí)到了一些請求思路,在業(yè)務(wù)復(fù)雜的場景下也是很需要這類請求工具來讓開發(fā)者的注意力從請求處理轉(zhuǎn)移集中在業(yè)務(wù)邏輯中。
以上就是學(xué)習(xí)ahooks useRequest并實(shí)現(xiàn)手寫的詳細(xì)內(nèi)容,更多關(guān)于ahooks useRequest手寫的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React實(shí)現(xiàn)動態(tài)輪播圖的使用示例
輪播組件是常見的一種方式,用來展示圖像、信息或者是廣告,本文就來介紹一下React實(shí)現(xiàn)動態(tài)輪播圖的使用示例,具有一定的參考價值,感興趣的可以了解一下2023-12-12React-router 4 按需加載的實(shí)現(xiàn)方式及原理詳解
本篇文章主要介紹了React-router 4 按需加載的實(shí)現(xiàn)方式及原理詳解,非常具有實(shí)用價值,需要的朋友可以參考下2017-05-05基于visual studio code + react 開發(fā)環(huán)境搭建過程
今天通過本文給大家分享基于visual studio code + react 開發(fā)環(huán)境搭建過程,本文給大家介紹的非常詳細(xì),包括react安裝問題及安裝 Debugger for Chrome的方法,需要的朋友跟隨小編一起看看吧2021-07-07React內(nèi)部實(shí)現(xiàn)cache方法示例詳解
這篇文章主要為大家介紹了React內(nèi)部實(shí)現(xiàn)cache方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11在React中強(qiáng)制重新渲染的4 種方式案例代碼
這篇文章主要介紹了在React中強(qiáng)制重新渲染的4 種方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-12-1240行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理
這篇文章主要介紹了40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05