React/Redux/Saga 分类接口无限请求循环问题求助
解决Redux Saga分类接口无限循环调用问题
问题背景
项目基于React/TypeScript、Redux和Saga开发,需获取分类列表渲染导航栏。通过useEffect分发Redux Action触发数据请求,但出现分类接口被调用数百次的无限循环问题,尝试Stack Overflow现有方案后仍未解决。
核心问题分析
无限循环的根源在于Action职责混淆+Saga监听逻辑错误:
- 同一个
GET_CATEGORIESAction既被用于触发Saga的API请求,又被用于将请求结果存入Reducer - Saga中使用
takeEvery(GET_CATEGORIES, getCategoriesTree),意味着每次dispatch这个Action都会触发一次API请求 - 当Saga请求完成后,会再次
put(getCategories(categoryTreeDTO)),这又会触发Saga的监听逻辑,形成无限循环
此外,Reducer初始状态设为数组[],后续却更新为对象{...state, payload: action.payload},存在类型不一致问题,可能引发额外渲染。
修复步骤
1. 拆分Action类型与职责
新增专门用于触发请求的Action,将“触发请求”和“设置数据”的动作分离:
修改actionTypes.ts
export const FETCH_CATEGORIES = 'FETCH_CATEGORIES'; // 触发请求 export const SET_CATEGORIES = 'SET_CATEGORIES'; // 设置数据
修改Actions文件
import { FETCH_CATEGORIES, SET_CATEGORIES } from './actionTypes'; // 触发分类数据请求 export const fetchCategories = () => { return { type: FETCH_CATEGORIES, }; }; // 将请求到的分类数据存入Redux export const setCategories = (categories: any) => { return { type: SET_CATEGORIES, payload: categories, }; };
2. 调整Saga监听与处理逻辑
让Saga监听新增的FETCH_CATEGORIES Action,请求完成后分发SET_CATEGORIES而非原Action:
修改Saga文件
import { FETCH_CATEGORIES } from './actionTypes'; import { setCategories } from './actions'; let categoriesApiService = container.resolve(CategoriesApiService); const categoryApi = async () => { return firstValueFrom( categoriesApiService.GetCategoryTree({ path: { version: '1' }, query: {}, }) ); }; function* getCategoriesTree() { try { let categoryTreeDTO: CategoryTreeDTO = yield call(categoryApi); yield put(setCategories(categoryTreeDTO)); } catch (error: any) { yield put(apiError(error?.response?.data?.message)); } } export function* watchGetCategories() { yield takeEvery(FETCH_CATEGORIES, getCategoriesTree); } function* categorySaga() { yield all([fork(watchGetCategories)]); } export default categorySaga;
3. 修正Reducer初始状态与更新逻辑
将初始状态统一为对象类型,避免类型混乱:
修改Reducer文件
import { SET_CATEGORIES } from './actionTypes'; const initialState = { payload: [], }; const reducer = (state = initialState, action) => { switch (action.type) { case SET_CATEGORIES: return { ...state, payload: action.payload, }; default: return state; } }; export default reducer;
4. 调整组件中的dispatch逻辑
组件中分发fetchCategories触发请求,无需传递现有categories参数:
修改TSX组件代码
const dispatch = useDispatch(); const categories = useSelector((state) => state?.categories?.payload); const loadCategories = () => { dispatch(fetchCategories()); }; useEffect(() => { loadCategories(); }, []); {categories?.map((x, index) => ( <Link key={index} href={`/store/${x.name}/s`}> <a type="button" id={x.name} title={x.name} className="xl:px-3 px-2 py-[1.15rem] font-normal" > {x.name} </a> </Link> ))}
额外优化点
- 为
map渲染的元素添加key属性,符合React渲染规范 - Reducer默认分支无需拷贝原state,直接返回即可,减少不必要的状态更新
内容的提问来源于stack exchange,提问作者Jamie Garcia
相关产品推荐
相关产品推荐

