Redux实践疑问:单Action Creator多分发或单Action改多属性?数据获取方案选哪种?
Hey there! Let's tackle your two Redux questions one by one—they're both super common and key to keeping your codebase maintainable and predictable.
1. 单个Action Creator分发多个Action,还是单个Action Type修改多个属性?
There's no one-size-fits-all answer, but it all boils down to semantics and how tightly linked the state changes are:
Use a single action type to update multiple properties if...
The state changes are directly tied to one specific business event. For example, when a user logs in successfully, you might need to updateuserInfo,isAuthenticated, andloadingall at once. A singleLOGIN_SUCCESSaction makes sense here because all those state updates are part of the same "user logged in" event. This keeps your reducer logic focused and makes it easy to trace exactly what changes happen when that event fires.Dispatch multiple actions from one creator if...
The state changes are relatively independent, or you want to reuse existing action logic. Let's say you already have separateSET_LOADINGandSET_USERactions defined. Instead of creating a newLOGIN_SUCCESSaction that duplicates their logic, you can dispatch both from your login action creator:const loginSuccess = (userData) => (dispatch) => { dispatch({ type: 'SET_LOADING', payload: false }); dispatch({ type: 'SET_USER', payload: userData }); };This follows the single responsibility principle—each action does one specific thing—and avoids redundant code.
The golden rule: Make sure every action represents a clear, meaningful event. Never cram unrelated state changes (like updating user data and changing app theme) into a single action, even if they happen to occur at the same time.
2. 数据库数据获取:Action Creator+Reducer vs 分离获取与数据逻辑的Reducer?
Short answer: Always keep async data fetching logic in action creators (or async middleware like Redux Toolkit's createAsyncThunk) and leave reducers to handle only pure, synchronous state updates.
Here's why: Redux reducers must be pure functions—no side effects like API calls, database queries, or async operations allowed. If you stick fetch logic inside a reducer, you break this core rule, leading to unpredictable state changes, hard-to-debug bugs, and untestable code.
A clean approach using Redux Toolkit (the modern standard for Redux) looks like this:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; // Async action creator: handles all the database fetch logic export const fetchUserFromDB = createAsyncThunk( 'user/fetchUserFromDB', async (userId) => { // This is where your database/API call lives const response = await db.query(`SELECT * FROM users WHERE id = ${userId}`); return response.rows[0]; } ); // Reducer: only handles synchronous state updates const userSlice = createSlice({ name: 'user', initialState: { data: null, loading: false, error: null }, reducers: {}, extraReducers: (builder) => { builder // Handle the "fetch started" state .addCase(fetchUserFromDB.pending, (state) => { state.loading = true; state.error = null; }) // Handle successful fetch .addCase(fetchUserFromDB.fulfilled, (state, action) => { state.loading = false; state.data = action.payload; }) // Handle fetch failure .addCase(fetchUserFromDB.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }); } }); export default userSlice.reducer;
This setup has huge benefits:
- Separation of concerns: Async logic lives in one place, state updates in another—your code stays organized.
- Predictable state changes: Reducers remain pure, so you can trust that given the same action and state, they'll always return the same new state.
- Less boilerplate: Redux Toolkit handles the pending/fulfilled/rejected action types automatically, so you don't have to write them manually.
Putting fetch logic in reducers is a big anti-pattern—avoid it at all costs. It'll make your code harder to maintain and debug down the line.
内容的提问来源于stack exchange,提问作者Nick Manning

