女装网站功能的建设,恒丰建设集团有限公司 网站,池州网站建设有哪些公司,利川市网站建设前言
最近业务没有之前紧张了#xff0c;也是消失了一段时间#xff0c;也总结了一些之前业务上的问题。
和同事沟通也是发现普通的async await 封装api在复杂业务场景下针对于请求的业务逻辑比较多#xff0c;也是推荐我去学习一波ahooks#xff0c;由于问题起源于请求…前言
最近业务没有之前紧张了也是消失了一段时间也总结了一些之前业务上的问题。
和同事沟通也是发现普通的async await 封装api在复杂业务场景下针对于请求的业务逻辑比较多也是推荐我去学习一波ahooks由于问题起源于请求因此作者也是直接从 useRequest 开始看起。
附ahooks useRequest链接:
https://ahooks-v2.js.org/zh-CN/hooks/async/
实现
话不多说手写直接开始参考几个比较常用的 useRequest 能力来一个个实现吧。
基础版雏形
先上代码
useRequest.ts
interface UseRequestOptionsProps {/** 请求参数*/initialData?: object;/** 请求成功回调*/onSuccess?: (res: any) void;
}const useRequest (requestFn: (initialData?: object | string | [],) PromiseSetStateActionany,options: UseRequestOptionsProps,
) {const [data, setData] useStateSetStateActionany(null);const [loading, setLoading] useStateboolean(false);const [error, setError] useStatestring | null(null);const { initialData, onSuccess } options;useEffect(() {setLoading(true);setError(null);setData(null);request();}, [requestFn]);// useRequest业务逻辑const request async () {try {const res await requestFn(initialData);setData(res);// 请求成功响应回调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 对于请求函数的写法并无过多要求只要是一个异步function且返回一个promise对象即可传入useRequest的第一个参数中而第二个参数则是一系列的可选配置项雏形版本我们暂时只支持onSuccess。
手动触发
代码改造后
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 请求成功回调*/onSuccess?: (res: any) void;
}const useRequest (requestFn: (initialData?: object | string | [],) PromiseSetStateActionany,options: UseRequestOptionsProps,
) {const [data, setData] useStateSetStateActionany(null);const [loading, setLoading] useStateboolean(false);const [error, setError] useStatestring | null(null);const { manual, initialData, onSuccess } options;useEffect(() {setLoading(true);setError(null);setData(null);!manual request();}, [manual]);// useRequest业务逻辑const request async () {try {const res await requestFn(initialData);setData(res);// 请求成功响应回调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();手动执行的逻辑主要是根据manual参数砍掉useRequest mount阶段的渲染请求把执行请求的能力暴露出去在页面中去手动调用request()来触发。
轮询与手动取消
代码改造后
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 请求成功回调*/onSuccess?: (res: any) void;
}const useRequest (requestFn: (initialData?: object | string | [],) PromiseSetStateActionany,options: UseRequestOptionsProps,
) {const [data, setData] useStateSetStateActionany(null);const [loading, setLoading] useStateboolean(false);const [error, setError] useStatestring | null(null);const status useRefboolean(false);const pollingIntervalTimer useRefNodeJS.Timer | null(null);const { manual, initialData, pollingInterval, onSuccess } options;useEffect(() {setLoading(true);setError(null);setData(null);!manual request();}, [manual]);// useRequest业务逻辑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);// 请求成功响应回调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();...
// 轮询到理想数据后
cancel();
轮询的支持在hook中主要用到了timer setTimeout的递归思路同时给出一个status状态值判断是否在轮询中当调用端执行cancel()status则为false当轮询开始则status为true。
而cancel()的能力主要也是取消了timer的递归请求逻辑并且轮询的业务场景和manual: true配合很多。
依赖请求串型请求
代码改造后
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 准备用于依赖请求*/ready?: boolean;/** 请求成功回调*/onSuccess?: (res: any) void;
}const useRequest (requestFn: (initialData?: object | string | [],) PromiseSetStateActionany,options: UseRequestOptionsProps,
) {const [data, setData] useStateSetStateActionany(null);const [loading, setLoading] useStateboolean(false);const [error, setError] useStatestring | null(null);const status useRefboolean(false);const pollingIntervalTimer useRefNodeJS.Timer | null(null);const {manual,initialData,pollingInterval,ready true,onSuccess,} options;useEffect(() {setLoading(true);setError(null);setData(null);!manual ready request();}, [manual, ready]);// useRequest业务逻辑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);// 请求成功响应回调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] useStateboolean(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加载时是否做默认请求而当option中的ready更新为true时hook自动更新从而发起请求。
常用于页面中A请求完成后执行B请求B请求的ready字段依赖于A请求的data/loading字段。
防抖与节流
防抖和节流的实现比较简单依赖于lodash库包装了一下request函数的请求内容。
代码如下
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 准备用于依赖请求*/ready?: boolean;/** 防抖*/debounceInterval?: number;/** 节流*/throttleInterval?: number;/** 请求成功回调*/onSuccess?: (res: any) void;
}const useRequest (requestFn: (initialData?: object | string | [],) PromiseSetStateActionany,options: UseRequestOptionsProps,
) {const [data, setData] useStateSetStateActionany(null);const [loading, setLoading] useStateboolean(false);const [error, setError] useStatestring | null(null);const status useRefboolean(false);const pollingIntervalTimer useRefNodeJS.Timer | null(null);const {manual,initialData,pollingInterval,ready true,debounceInterval,throttleIntervalonSuccess,} 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业务逻辑
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);// 请求成功响应回调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, // 节流onSuccess: (res) {console.log(success request!, res);},},
);for(let i 0; i 10000; i) {request();
}在hook中通过lodash.debounce/lodash.throttle来包装request函数主体通过option中的判断来执行对应的包装体函数。
缓存与依赖更新
改造后的代码最终代码如下
useRequest.ts
import {useState,useEffect,useRef,SetStateAction,useCallback,
} from react;
import lodash from lodash;interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 准备用于依赖请求*/ready?: boolean;/** 防抖*/debounceInterval?: number;/** 节流*/throttleInterval?: number;/** 延迟loading为true的时间*/loadingDelay?: number;/** 依赖*/refreshDeps?: any[];/** 请求成功回调*/onSuccess?: (res: any) void;
}const useRequest (requestFn: (initialData?: object | string | [],) PromiseSetStateActionany,options: UseRequestOptionsProps,
) {const [data, setData] useStateSetStateActionany(null);const [loading, setLoading] useStateboolean(false);const [error, setError] useStatestring | null(null);const status useRefboolean(false);const pollingIntervalTimer useRefNodeJS.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);// 手动触发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业务逻辑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);// 请求成功响应回调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] useStateboolean(false);
const [updateLoading, setUpdateLoading] useStateboolean(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, // 节流refreshDeps: [mountLoading, updateLoading],onSuccess: (res) {console.log(success request!, res);},},
);缓存的主体思路是在useRequest中拿到第一次数据后通过useCallback来透出data依赖来保存同时向外暴露一个cachedFetchData来过渡data从null到请求到接口数据的过程。
依赖更新的思路则是在页面中给useRequest一系列依赖状态一并加入在hook的请求副作用中监听到页面中依赖改变则重新请求具体实现则是refreshDeps参数。
结尾
花了一上午时间一个简易版本的useRequest实现了也是通过实现学习到了一些请求思路在业务复杂的场景下也是很需要这类请求工具来让开发者的注意力从请求处理转移集中在业务逻辑中。