You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 19:48:17