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

Redux reducer与服务端API协作及redux-actions的reducer创建最佳实践

Hey there! Let's dive into your Redux questions — I’ve got plenty of hands-on experience with redux-actions and API integrations, so I’ll share practical, actionable approaches here.


1. How to Coordinate Redux Reducers with Server-Side APIs?

First off, remember that reducers are pure functions — they can’t perform side effects like API calls directly. So we need to use middleware to handle async logic, then feed the results back to reducers via plain action objects. Here’s a standard, battle-tested workflow:

  • Split responsibilities clearly:

    • Reducers: Only update state immutably based on sync action objects (no async code allowed here).
    • Async logic: Handled in action creators (with middleware like redux-thunk) or dedicated async layers (like redux-saga for more complex flows).
  • Practical example with redux-thunk:
    Let’s say you want to fetch a list of items from an API. First, define your action types and creators (using redux-actions for consistency):

    import { createAction } from 'redux-actions';
    
    // Action types
    const FETCH_ITEMS_REQUEST = 'FETCH_ITEMS_REQUEST';
    const FETCH_ITEMS_SUCCESS = 'FETCH_ITEMS_SUCCESS';
    const FETCH_ITEMS_FAILURE = 'FETCH_ITEMS_FAILURE';
    
    // Sync action creators
    const fetchItemsRequest = createAction(FETCH_ITEMS_REQUEST);
    const fetchItemsSuccess = createAction(FETCH_ITEMS_SUCCESS);
    const fetchItemsFailure = createAction(FETCH_ITEMS_FAILURE);
    
    // Async action creator (thunk)
    export const fetchItems = () => async (dispatch) => {
      dispatch(fetchItemsRequest()); // Signal we're starting the request
      try {
        const response = await fetch('/api/items');
        const data = await response.json();
        dispatch(fetchItemsSuccess(data)); // Pass data to reducer on success
      } catch (error) {
        dispatch(fetchItemsFailure(error.message)); // Pass error on failure
      }
    };
    

    Then your reducer handles these actions to update state:

    import { handleActions } from 'redux-actions';
    
    const initialState = {
      items: [],
      loading: false,
      error: null
    };
    
    const itemsReducer = handleActions(
      {
        [FETCH_ITEMS_REQUEST]: (state) => ({
          ...state,
          loading: true,
          error: null
        }),
        [FETCH_ITEMS_SUCCESS]: (state, { payload }) => ({
          ...state,
          loading: false,
          items: payload
        }),
        [FETCH_ITEMS_FAILURE]: (state, { payload }) => ({
          ...state,
          loading: false,
          error: payload
        })
      },
      initialState
    );
    
  • Core flow:

    1. Component dispatches the async action (fetchItems()).
    2. Thunk middleware runs the async logic, dispatching a "request" action first to set loading state.
    3. On API success/failure, the thunk dispatches a "success" or "failure" action.
    4. Reducer responds to these plain actions, updating the state immutably.
    5. Component re-renders with the updated state.

If you need more control over async flows (like canceling requests or debouncing), redux-saga is a great alternative — but the core idea remains the same: async logic lives outside reducers, and reducers only handle state updates from sync actions.


2. Optimal Ways to Create Reducers with handleActions in redux-actions

Both approaches you mentioned have their use cases — let’s walk through each with concrete examples.

(1) Separate Reducers for Each CRUD Operation, Then Combine Them

This approach works best when your CRUD operations have distinct state concerns (e.g., adding state vs. deleting state don’t overlap much). It keeps each reducer focused, easier to test, and simpler to debug.

How to Set initialState:

Each individual reducer manages its own slice of the state, so you define initialState per reducer, matching exactly what that reducer needs to track. Then use combineReducers to merge them into a root reducer.

Example:

import { handleActions, createAction } from 'redux-actions';
import { combineReducers } from 'redux';

// Action types for CRUD
const ADD_ITEM_REQUEST = 'ADD_ITEM_REQUEST';
const ADD_ITEM_SUCCESS = 'ADD_ITEM_SUCCESS';
const DELETE_ITEM_REQUEST = 'DELETE_ITEM_REQUEST';
const DELETE_ITEM_SUCCESS = 'DELETE_ITEM_SUCCESS';

// Action creators
export const addItemRequest = createAction(ADD_ITEM_REQUEST);
export const addItemSuccess = createAction(ADD_ITEM_SUCCESS);
export const deleteItemRequest = createAction(DELETE_ITEM_REQUEST);
export const deleteItemSuccess = createAction(DELETE_ITEM_SUCCESS);

// Add Item Reducer (tracks add-specific state)
const addInitialState = {
  isAdding: false,
  addError: null
};

const addReducer = handleActions(
  {
    [ADD_ITEM_REQUEST]: (state) => ({ ...state, isAdding: true, addError: null }),
    [ADD_ITEM_SUCCESS]: (state) => ({ ...state, isAdding: false })
  },
  addInitialState
);

// Delete Item Reducer (tracks delete-specific state)
const deleteInitialState = {
  deletingId: null,
  deleteError: null
};

const deleteReducer = handleActions(
  {
    [DELETE_ITEM_REQUEST]: (state, { payload }) => ({ ...state, deletingId: payload }),
    [DELETE_ITEM_SUCCESS]: (state) => ({ ...state, deletingId: null })
  },
  deleteInitialState
);

// Data Reducer (manages the actual list of items)
const dataInitialState = [];
const dataReducer = handleActions(
  {
    [ADD_ITEM_SUCCESS]: (state, { payload }) => [...state, payload],
    [DELETE_ITEM_SUCCESS]: (state, { payload }) => state.filter(item => item.id !== payload)
  },
  dataInitialState
);

// Combine all reducers into a root reducer
export const rootReducer = combineReducers({
  add: addReducer,
  delete: deleteReducer,
  items: dataReducer
});

Why this works:

  • Each reducer has a single responsibility (e.g., addReducer only tracks add-related loading/error state).
  • initialState is scoped to what each reducer needs, avoiding bloated, hard-to-maintain state objects.
  • Easier to test — you can test each reducer in isolation without worrying about unrelated state.

This approach is better for smaller features where your CRUD operations share closely related state (e.g., you want to track a single loading state, or all actions affect the same data list). It keeps all logic for a feature centralized in one place.

Example:

import { handleActions, createAction } from 'redux-actions';

// Action types
const FETCH_DELETE_DATA_REQUEST = 'FETCH_DELETE_DATA_REQUEST';
const FETCH_DELETE_DATA_SUCCESS = 'FETCH_DELETE_DATA_SUCCESS';
const FETCH_ADD_DATA_REQUEST = 'FETCH_ADD_DATA_REQUEST';
const FETCH_ADD_DATA_SUCCESS = 'FETCH_ADD_DATA_SUCCESS';

// Action creators
export const fetchDeleteDataRequest = createAction(FETCH_DELETE_DATA_REQUEST);
export const fetchDeleteDataSuccess = createAction(FETCH_DELETE_DATA_SUCCESS);
export const fetchAddDataRequest = createAction(FETCH_ADD_DATA_REQUEST);
export const fetchAddDataSuccess = createAction(FETCH_ADD_DATA_SUCCESS);

// Combined reducer for all CRUD actions
const initialState = {
  data: [],
  isLoading: false,
  deletingId: null, // Track which item is being deleted
  addingItem: null // Track which item is being added
};

const crudReducer = handleActions(
  {
    [FETCH_ADD_DATA_REQUEST]: (state, { payload }) => ({
      ...state,
      isLoading: true,
      addingItem: payload
    }),
    [FETCH_ADD_DATA_SUCCESS]: (state, { payload }) => ({
      ...state,
      isLoading: false,
      addingItem: null,
      data: [...state.data, payload]
    }),
    [FETCH_DELETE_DATA_REQUEST]: (state, { payload }) => ({
      ...state,
      isLoading: true,
      deletingId: payload
    }),
    [FETCH_DELETE_DATA_SUCCESS]: (state, { payload }) => ({
      ...state,
      isLoading: false,
      deletingId: null,
      data: state.data.filter(item => item.id !== payload)
    })
  },
  initialState
);

export default crudReducer;

Why this works:

  • All logic for the feature is centralized — easy to see how each action affects the state at a glance.
  • Reduces boilerplate for small features where splitting into multiple reducers would add unnecessary complexity.
  • You can share state between actions (e.g., a single isLoading flag for all CRUD operations, if that makes sense for your UI).

内容的提问来源于stack exchange,提问作者VladislavKh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 14:12:31