學(xué)習(xí)ahooks useRequest并實(shí)現(xiàn)手寫
前言
最近業(yè)務(wù)沒有之前緊張了,也是消失了一段時(shí)間,也總結(jié)了一些之前業(yè)務(wù)上的問題。
和同事溝通也是發(fā)現(xiàn)普通的async + await + 封裝api在復(fù)雜業(yè)務(wù)場景下針對于請求的業(yè)務(wù)邏輯比較多,也是推薦我去學(xué)習(xí)一波ahooks,由于問題起源于請求,因此作者也是直接從 useRequest 開始看起。
附ahooks useRequest鏈接:
實(shí)現(xiàn)
話不多說,手寫直接開始,參考幾個(gè)比較常用的 useRequest 能力來一個(gè)個(gè)實(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ù)的寫法并無過多要求,只要是一個(gè)異步function且返回一個(gè)promise對象,即可傳入useRequest的第一個(gè)參數(shù)中,而第二個(gè)參數(shù)則是一系列的可選配置項(xiàng),雛形版本我們暫時(shí)只支持onSuccess。
手動(dòng)觸發(fā)
代碼改造后:
useRequest.ts
interface UseRequestOptionsProps {
/*
* 手動(dòng)開啟
*/
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();
手動(dòng)執(zhí)行的邏輯主要是根據(jù)manual參數(shù)砍掉useRequest mount階段的渲染請求,把執(zhí)行請求的能力暴露出去,在頁面中去手動(dòng)調(diào)用request()來觸發(fā)。
輪詢與手動(dòng)取消
代碼改造后:
useRequest.ts
interface UseRequestOptionsProps {
/*
* 手動(dòng)開啟
*/
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的遞歸思路,同時(shí)給出一個(gè)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 {
/*
* 手動(dòng)開啟
*/
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中加入一個(gè)ready字段,也是在基于manual一層的限制后又加了一層,來判斷是否在hook加載時(shí)是否做默認(rèn)請求,而當(dāng)option中的ready更新(為true)時(shí),hook自動(dòng)更新從而發(fā)起請求。
常用于頁面中A請求完成后執(zhí)行B請求,B請求的ready字段依賴于A請求的data/loading字段。
防抖與節(jié)流
防抖和節(jié)流的實(shí)現(xiàn)比較簡單,依賴于lodash庫,包裝了一下request函數(shù)的請求內(nèi)容。
代碼如下:
useRequest.ts
interface UseRequestOptionsProps {
/*
* 手動(dòng)開啟
*/
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 {
/*
* 手動(dòng)開啟
*/
manual?: boolean;
/*
* 請求參數(shù)
*/
initialData?: object;
/*
* 輪詢
*/
pollingInterval?: number | null;
/*
* 準(zhǔn)備,用于依賴請求
*/
ready?: boolean;
/*
* 防抖
*/
debounceInterval?: number;
/*
* 節(jié)流
*/
throttleInterval?: number;
/*
* 延遲loading為true的時(shí)間
*/
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);
// 手動(dòng)觸發(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依賴來保存,同時(shí)向外暴露一個(gè)cachedFetchData來過渡data從null到請求到接口數(shù)據(jù)的過程。
依賴更新的思路則是在頁面中給useRequest一系列依賴狀態(tài)一并加入在hook的請求副作用中,監(jiān)聽到頁面中依賴改變,則重新請求,具體實(shí)現(xiàn)則是refreshDeps參數(shù)。
結(jié)尾
花了一上午時(shí)間,一個(gè)簡易版本的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ù)組件useState異步,數(shù)據(jù)不能及時(shí)獲取到的問題
這篇文章主要介紹了react函數(shù)組件useState異步,數(shù)據(jù)不能及時(shí)獲取到的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
React+Node.js實(shí)現(xiàn)大文件傳輸與斷點(diǎn)續(xù)傳
這篇文章主要為大家詳細(xì)介紹了如何使用React前端和Node.js后端實(shí)現(xiàn)大文件傳輸和斷點(diǎn)續(xù)傳的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考下2024-12-12
Ant?Design?組件庫之步驟條實(shí)現(xiàn)
這篇文章主要為大家介紹了Ant?Design組件庫之步驟條實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
使用React和Redux Toolkit實(shí)現(xiàn)用戶登錄功能
在React中,用戶登錄功能是一個(gè)常見的需求,為了實(shí)現(xiàn)該功能,需要對用戶輸入的用戶名和密碼進(jìn)行驗(yàn)證,并將驗(yàn)證結(jié)果保存到應(yīng)用程序狀態(tài)中,在React中,可以使用Redux Toolkit來管理應(yīng)用程序狀態(tài),從而實(shí)現(xiàn)用戶登錄功能,需要詳細(xì)了解可以參考下文2023-05-05
React Hooks獲取數(shù)據(jù)實(shí)現(xiàn)方法介紹
這篇文章主要介紹了react hooks獲取數(shù)據(jù),文中給大家介紹了useState dispatch函數(shù)如何與其使用的Function Component進(jìn)行綁定,實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
基于React.js實(shí)現(xiàn)原生js拖拽效果引發(fā)的思考
這篇文章主要為大家詳細(xì)介紹了基于React.js實(shí)現(xiàn)原生js拖拽效果,繼而引發(fā)的一系列思考,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03

