如何在React组件中监听Redux Saga的成功/失败动作并响应
解决方案
针对你在React TypeScript + Redux Saga项目中遇到的「精准追踪请求完成状态并执行UI操作」的问题,推荐以下几种实用方案:
方案一:为单个请求添加专属状态(推荐)
核心思路是在Reducer中新增仅针对创建TODO请求的独立状态,避免与全局的isLoading/error状态混淆,精准追踪当前请求的结果。
1. 扩展Reducer类型与初始状态
export type TodoReducer = { readonly todos: Todo[], readonly isLoading: boolean, readonly error: Error | null, // 新增创建Todo的专属状态 createTodoStatus: 'idle' | 'loading' | 'success' | 'failure', createTodoError: Error | null }; const initialState: TodoReducer = { todos: [], isLoading: false, error: null, createTodoStatus: 'idle', createTodoError: null };
2. 更新Reducer处理逻辑
export default function todoReducer(state = initialState, action: TodoActions) { switch (action.type) { case CREATE_TODO_START: return { ...state, createTodoStatus: 'loading', createTodoError: null // 重置当前请求的错误状态 }; case CREATE_TODO_SUCCESS: return { ...state, todos: [...state.todos, action.payload], createTodoStatus: 'success' }; case CREATE_TODO_FAILURE: return { ...state, createTodoStatus: 'failure', createTodoError: action.payload }; // 新增重置状态的动作,避免重复触发UI操作 case 'CREATE_TODO_RESET': return { ...state, createTodoStatus: 'idle', createTodoError: null }; default: return state; } }
3. 在组件中监听状态并执行操作
import { useDispatch, useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import { message } from 'antd'; // 示例用Antd提示组件 const TodoForm = () => { const dispatch = useDispatch(); const navigate = useNavigate(); const { createTodoStatus, createTodoError } = useSelector((state: RootState) => state.todos); // 监听创建请求的状态变化 useEffect(() => { if (createTodoStatus === 'success') { navigate('/todo-dashboard'); dispatch({ type: 'CREATE_TODO_RESET' }); // 重置状态,避免重复跳转 } else if (createTodoStatus === 'failure') { message.error(createTodoError?.message || '创建TODO失败'); dispatch({ type: 'CREATE_TODO_RESET' }); } }, [createTodoStatus, createTodoError, navigate, dispatch]); const onFinish = (newTodo: NewTodoRequest) => { dispatch(createTodoStart(newTodo)); }; return ( {/* 你的表单组件 */} ); };
方案二:用唯一请求ID追踪状态(适合多请求并行场景)
如果需要同时处理多个创建请求,可通过为每个请求生成唯一ID,精准绑定请求与状态。
1. 修改Action结构,添加请求ID
export const createTodoStart = (payload: NewTodoRequest, requestId: string) => ({ type: CREATE_TODO_START, payload, meta: { requestId } });
2. 更新Reducer存储请求状态
export type TodoReducer = { todos: Todo[], isLoading: boolean, error: Error | null, pendingRequests: Record<string, 'loading' | 'success' | 'failure'>, requestErrors: Record<string, Error | null> }; const initialState: TodoReducer = { todos: [], isLoading: false, error: null, pendingRequests: {}, requestErrors: {} }; export default function todoReducer(state = initialState, action: TodoActions) { switch (action.type) { case CREATE_TODO_START: return { ...state, pendingRequests: { ...state.pendingRequests, [action.meta.requestId]: 'loading' }, requestErrors: { ...state.requestErrors, [action.meta.requestId]: null } }; case CREATE_TODO_SUCCESS: return { ...state, todos: [...state.todos, action.payload], pendingRequests: { ...state.pendingRequests, [action.meta.requestId]: 'success' } }; case CREATE_TODO_FAILURE: return { ...state, pendingRequests: { ...state.pendingRequests, [action.meta.requestId]: 'failure' }, requestErrors: { ...state.requestErrors, [action.meta.requestId]: action.payload } }; // 清理已完成的请求记录 case 'CLEAR_REQUEST': const { [action.payload]: _, ...remainingRequests } = state.pendingRequests; const { [action.payload]: __, ...remainingErrors } = state.requestErrors; return { ...state, pendingRequests: remainingRequests, requestErrors: remainingErrors }; default: return state; } }
3. 组件中生成并追踪请求ID
const TodoForm = () => { const dispatch = useDispatch(); const navigate = useNavigate(); const currentRequestId = useRef<string | null>(null); // 保存当前请求ID const { pendingRequests, requestErrors } = useSelector((state: RootState) => state.todos); useEffect(() => { if (!currentRequestId.current) return; const status = pendingRequests[currentRequestId.current]; if (status === 'success') { navigate('/todo-dashboard'); dispatch({ type: 'CLEAR_REQUEST', payload: currentRequestId.current }); currentRequestId.current = null; } else if (status === 'failure') { const error = requestErrors[currentRequestId.current]; message.error(error?.message || '创建TODO失败'); dispatch({ type: 'CLEAR_REQUEST', payload: currentRequestId.current }); currentRequestId.current = null; } }, [pendingRequests, requestErrors, navigate, dispatch]); const onFinish = (newTodo: NewTodoRequest) => { const requestId = crypto.randomUUID(); // 生成唯一ID currentRequestId.current = requestId; dispatch(createTodoStart(newTodo, requestId)); }; return ( {/* 你的表单组件 */} ); };
方案三:直接在Saga中处理UI操作(快速实现,耦合度较高)
如果项目规模较小,可直接在Saga中执行跳转或错误提示,但会导致业务逻辑与UI逻辑耦合,不推荐大型项目使用。
1. 创建全局History实例
// src/history.ts import { createBrowserHistory } from 'history'; export const history = createBrowserHistory();
2. 在Saga中使用History跳转
import { history } from './history'; import { message } from 'antd'; function* newTodo({payload}: NewTodoStart) { try { const {data} = yield call(createTodo, payload); yield put(createTodoSuccess(data)); history.push('/todo-dashboard'); // 跳转页面 } catch (error) { yield put(createTodoFailure(error as Error)); message.error((error as Error).message || '创建TODO失败'); // 显示错误提示 } }
3. 替换React Router的BrowserRouter
import { Router } from 'react-router-dom'; import { history } from './history'; ReactDOM.createRoot(document.getElementById('root')!).render( <Router history={history}> <App /> </Router> );
内容的提问来源于stack exchange,提问作者Aneesh Pothuru
相关产品推荐
相关产品推荐

