Redux-Saga API重复调用问题:原因排查与解决方法咨询
Redux-Saga + Redux-Toolkit API重复调用问题
问题现象
点击触发登录逻辑时,API调用次数等于userSaga中yield all内takeEvery的注册数量,即便在点击逻辑中加入了isLoading状态判断,重复调用问题依然存在。
相关代码
Reducer
import { createSlice } from "@reduxjs/toolkit"; import { User } from "../types"; import { initialState } from "../initialState"; export const userSlice = createSlice({ name: "user", initialState : initialState.user, reducers : { login : (state) => { state.isLoading = true, state.error = "" }, loginSuccess : (state, action) => { }, loginFailure : (state, action) => { state.isLoading = false }, register : (state) => { state.isLoading = true, state.error = "" }, registerSuccess : (state, action) => { }, registerFailure : (state, action) => { state.isLoading = false }, forgotPassword : (state) => { state.isLoading = true, state.error = "" }, forgotPasswordSuccess : (state, action) => { }, forgotPasswordFailure : (state, action) => { state.isLoading = false }, resendOtp : (state) => { state.isLoading = true, state.error = "" }, resendOtpSuccess : (state, action) => { }, resendOtpFailure : (state, action) => { state.isLoading = false }, verifyOtp : (state) => { state.isLoading = true, state.error = "" }, verifyOtpSuccess : (state, action) => { }, verifyOtpFailure : (state, action) => { state.isLoading = false }, resetPassword : (state) => { state.isLoading = true, state.error = "" }, resetPasswordSuccess : (state, action) => { }, resetPasswordFailure : (state, action) => { state.isLoading = false }, } }) export const userActions = userSlice.actions; export default userSlice.reducer;
Saga切片
import { all, call, cancel, put, takeEvery, takeLatest } from 'redux-saga/effects'; import AuthenticationApi from '../../utils/api/authentication'; import { userActions } from './userReducer'; import actions from '../sagaActions'; import { ResponseGenerator , Iaction } from '../sagaInterface'; export function* handelLogin(action: Iaction){ try{ let watcher = yield put(userActions.login()); const response: ResponseGenerator = yield AuthenticationApi.userLogin(action.payload); yield put(userActions.loginSuccess(response)); }catch(err){ yield put(userActions.loginFailure(err)); } } export function* handelRegister(action: Iaction){ try{ yield put(userActions.register()); const response: ResponseGenerator = yield AuthenticationApi.userRegistration(action.payload); yield put(userActions.registerSuccess(response)); }catch(err){ yield put(userActions.registerFailure(err)); } } export function* handelForgotPassword(action: Iaction){ try{ yield put(userActions.forgotPassword()); const response: ResponseGenerator = yield AuthenticationApi.forgotPassword(action.payload); yield put(userActions.forgotPasswordSuccess(response)); }catch(err){ yield put(userActions.forgotPasswordFailure(err)); } } export default function* userSaga(){ yield all([ takeEvery(actions.LOGIN , handelLogin), takeEvery(actions.REGISTER , handelRegister), takeEvery(actions.FORGOT_PASSWORD , handelForgotPassword) ]) }
触发逻辑
const isLoading = useSelector((state: state) => state.user.isLoading); const handelLoginClick = (login) => { if(!isLoading){ let toSend = { credencialType: useEmail ? 'email' : 'uniqueCode', userCredencial: login.userCredencial, userPassword: login.userPassword } dispatch({ type: actions.LOGIN, payload: toSend }) } }
问题原因
- Action循环触发:
handelLogin中调用yield put(userActions.login()),而该action恰好被takeEvery(actions.LOGIN)监听,导致每次执行handelLogin都会重新触发LOGIN action,形成循环调用,最终调用次数随监听数量叠加。 - Saga重复注册:如果
userSaga被多次注入到Redux store的saga中间件中(比如根saga重复引入、组件内多次调用sagaMiddleware.run),会导致每个takeEvery监听被重复注册,触发action时所有监听都会执行对应处理函数。 - Reducer状态更新不完整:
loginSuccess等成功action未将isLoading设为false,可能导致后续状态判断失效,但不会直接导致调用次数等于监听数量。
解决方案
1. 修复Action循环问题
移除saga处理函数中对监听action的重复触发,将loading状态更新移至组件触发时:
// 修改handelLogin export function* handelLogin(action: Iaction){ try{ // 移除这行,避免循环触发LOGIN action // let watcher = yield put(userActions.login()); // 用call包裹API调用,符合Redux-Saga规范 const response: ResponseGenerator = yield call(AuthenticationApi.userLogin, action.payload); yield put(userActions.loginSuccess(response)); }catch(err){ yield put(userActions.loginFailure(err)); } } // 修改组件触发逻辑 const handelLoginClick = (login) => { if(!isLoading){ let toSend = { credencialType: useEmail ? 'email' : 'uniqueCode', userCredencial: login.userCredencial, userPassword: login.userPassword } dispatch(userActions.login()); // 先更新loading状态 dispatch({ type: actions.LOGIN, payload: toSend }) } }
2. 统一Action类型,避免混淆
直接使用Redux-Toolkit生成的action类型作为监听目标,避免自定义actions.LOGIN导致的类型不一致:
export default function* userSaga(){ yield all([ takeEvery(userActions.login.type , handelLogin), takeEvery(userActions.register.type , handelRegister), takeEvery(userActions.forgotPassword.type , handelForgotPassword) ]) }
3. 确保Saga仅注册一次
检查根saga配置,确保userSaga只被引入一次:
// 根saga示例 import { all } from 'redux-saga/effects'; import userSaga from './user/userSaga'; export default function* rootSaga() { yield all([ userSaga(), // 其他saga仅引入一次 ]); }
且仅在store初始化时调用一次sagaMiddleware.run(rootSaga),不要在组件或其他地方重复调用。
4. 改用takeLatest避免重复执行
如果不需要同时处理多个相同action,用takeLatest替代takeEvery,新的action会取消未完成的任务:
export default function* userSaga(){ yield all([ takeLatest(userActions.login.type , handelLogin), takeLatest(userActions.register.type , handelRegister), takeLatest(userActions.forgotPassword.type , handelForgotPassword) ]) }
5. 完善Reducer状态更新
补充成功action的isLoading重置逻辑:
loginSuccess : (state, action) => { state.isLoading = false; // 处理用户数据,比如state.user = action.payload; }, // 同理补充registerSuccess、forgotPasswordSuccess等
内容的提问来源于stack exchange,提问作者AMIT KUMAR cuHLItFZDW
相关产品推荐
相关产品推荐

