React useApi Hook在useEffect中使用触发无限循环的解决方案咨询
React useApi Hook在useEffect中使用触发无限循环的解决方案咨询
我来帮你分析下这个问题的根源,然后一步步给出可行的解决方案~
问题根源
你遇到的无限循环,本质是因为每次组件渲染时,useApi Hook都会返回一个全新的对象(里面的request、cancel等方法都是重新创建的)。当你把vacancyApi放进useEffect的依赖数组时,组件每次渲染都会检测到依赖变化,进而触发useEffect,调用request后又会更新组件状态,再次触发渲染,形成循环。
你之前尝试用useCallback包裹request是对的思路,但还需要补充一些细节才能彻底解决问题,下面是完整的优化方案:
第一步:优化useApi Hook内部实现
我们需要用useCallback缓存request和cancel方法,再用useMemo缓存整个返回对象,确保只有当必要的依赖变化时,才更新这些方法和对象的引用。另外还要补上你代码里缺失的cancel函数实现:
import { useState, useRef, useCallback, useMemo } from 'react'; import { AxiosResponse } from 'axios'; function useApi<T>(apiFunc: (...args: Array<any>) => Promise<AxiosResponse<T>>) { const [data, setData] = useState<T | null>(null); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const controllerRef = useRef(new AbortController()); // 用useCallback缓存cancel方法,确保引用稳定 const cancel = useCallback(() => { controllerRef.current.abort(); // 可选:取消后重置状态 setLoading(false); setError("Request cancelled"); }, []); // 用useCallback缓存request方法,依赖仅为传入的apiFunc const request = useCallback(async (...args: Array<any>) => { setLoading(true); setError(""); // 每次请求前创建新的AbortController,避免旧的取消信号影响新请求 controllerRef.current = new AbortController(); try { // 把AbortController的signal传给apiFunc,支持取消请求 const response = await apiFunc(...args, { signal: controllerRef.current.signal }); setData(response.data); } catch (error: any) { // 区分用户主动取消的错误和其他错误,避免误显示取消错误 if (error.name !== 'AbortError') { setError(error.message || 'Unknown Error'); } } finally { setLoading(false); } }, [apiFunc]); // 用useMemo缓存整个返回对象,只有当内部状态或回调变化时才更新 return useMemo(() => ({ request, cancel, data, error, loading }), [request, cancel, data, error, loading]); }
第二步:确保传入useApi的apiFunc引用稳定
如果你的getVacancy函数是定义在组件内部的,每次组件渲染都会创建新的getVacancy实例,这会导致useApi里的request方法重新创建,进而触发useEffect。所以需要用useCallback缓存getVacancy:
const getVacancy = useCallback(async (id: string) => { // 这里是你的API请求逻辑,比如: return axios.get(`/api/vacancies/${id}`); }, []); // 空依赖数组确保函数引用稳定
第三步:优化组件中的useEffect
现在你可以安全地在useEffect中依赖vacancyApi.request(而不是整个vacancyApi对象),这样只有当id变化或request方法引用变化时才会触发,同时加上清理函数取消未完成的请求:
const vacancyApi = useApi(getVacancy); useEffect(() => { if (!!id) { vacancyApi.request(id); } // 清理函数:组件卸载或id变化时,取消未完成的请求 return () => { vacancyApi.cancel(); }; }, [id, vacancyApi.request]); // 仅依赖id和稳定的request方法
为什么这样能解决问题?
useCallback让request和cancel的引用只有在依赖变化时才更新,避免每次渲染都生成新方法。useMemo确保useApi返回的对象引用稳定,只有当内部状态或回调变化时才更新。- 依赖
vacancyApi.request而不是整个对象,进一步缩小了触发useEffect的条件,只有id变化或request真的更新时才会执行。
这样就能实现仅当id变化时调用API,同时严格遵循React的依赖规则,不用再忽略ESLint的提示啦~
备注:内容来源于stack exchange,提问作者Hans Dabi
相关产品推荐
相关产品推荐

