React Hook中自定义Hook与Reducer引发无限循环问题求助
React Hook自定义Hook结合Reducer的无限循环问题排查与解决
问题根源分析
无限循环触发原因:
useFetch中的两个useEffect均未将dispatch纳入依赖数组。React组件渲染时,若useEffect依赖项发生变化会重新执行回调;当CommonContext状态更新后,dispatch可能生成新引用,触发useEffect重复执行,而dispatch(setLoading)又会引发上下文状态更新,形成循环。- 第二个
useEffect监听error状态,每次setErr后调用dispatch(setError),会再次触发上下文状态更新,加剧循环风险。
异步函数外调用
setLoading(false)提前执行:
若将dispatch(setLoading(false))放在异步函数外部,代码会在await axios.get(url)执行前就调用该方法,导致加载状态提前结束,不符合异步请求的生命周期逻辑。
解决方案
- 补全
useEffect依赖项:将dispatch添加到所有用到它的useEffect依赖数组中,确保依赖项完整,避免不必要的重复执行。 - 使用
AbortController处理请求取消:防止组件卸载后仍执行异步回调中的dispatch,避免内存泄漏和无效状态更新。 - 合并错误处理逻辑:将错误状态的
dispatch直接放在catch块中,移除单独监听error的useEffect,减少不必要的状态更新触发点。
修改后的完整代码
reducer代码(无需修改)
import { SET_ERROR, SET_LOADING } from "./common.type"; export const commonInitial = { isLoading: false, isError: { message: "", status: "", }, }; const commonReducer = (state: any, action: any) => { switch (action.type) { case SET_LOADING: return { ...state, isLoading: action.payload }; case SET_ERROR: return { ...state, isError: action.payload }; default: return { ...state }; } }; export default commonReducer;
自定义Hook useFetch 修改版
import { useState, useEffect, useContext } from "react"; import axios from "axios"; import { CommonContext } from "./path-to-common-context"; // 替换为实际上下文路径 import { SET_LOADING, SET_ERROR } from "./common.type"; export const useFetch = (url: string) => { const [response, setResponse] = useState([]); const { dispatch } = useContext(CommonContext); useEffect(() => { const abortController = new AbortController(); const signal = abortController.signal; const fetchDataFromUrl = async () => { dispatch({ type: SET_LOADING, payload: true }); try { const { data } = await axios.get(url, { signal }); setResponse(data); dispatch({ type: SET_LOADING, payload: false }); } catch (err: any) { if (!signal.aborted) { dispatch({ type: SET_LOADING, payload: false }); dispatch({ type: SET_ERROR, payload: { message: err.message, status: err.response?.status || "unknown" } }); } } }; fetchDataFromUrl(); // 组件卸载时取消请求 return () => abortController.abort(); }, [url, dispatch]); return [response]; };
Hook调用代码(无需修改)
// 使用自定义Hook获取数据 const [data] = useFetch("https://jsonplaceholder.typicode.com/posts"); return ( <div> <ul> {!!data?.length && data.map((item: Post) => { return ( <div key={item.id}> <Link to={`/post/${item.id}`}>Moves to Post {item.id}</Link> </div> ); })} </ul> </div> ); }; export default DemoHookA;
内容的提问来源于stack exchange,提问作者Trung Phan
相关产品推荐
相关产品推荐

