使用Redux如何避免Reducer中Switch语句膨胀?附Angular 5报错问题
Hey there! Let's tackle your two main ng2-Redux issues one by one—first that frustrating initialization error with combineReducers, then the growing switch statement bloat in your reducers.
This error pops up because every reducer must return a valid state when it receives an undefined initial state (which happens automatically when Redux initializes). If your reducer doesn't handle this case, it returns undefined, and Redux throws that warning.
Here's how to fix it:
- Define an initial state for each reducer and set it as the default value for the
stateparameter. - Always include a
defaultcase in your switch statement that returns the current state (even if it's the initial state).
Example of a broken reducer (causes the error):
// ❌ No initial state, no default case function appReducer(state, action) { switch(action.type) { case 'UPDATE_DATA': return {...state, data: action.payload}; // Missing default return } }
Fixed version:
// ✅ Explicit initial state and default case const initialAppState = { data: [], isLoading: false }; function appReducer(state = initialAppState, action) { switch(action.type) { case 'UPDATE_DATA': return {...state, data: action.payload}; case 'SET_LOADING': return {...state, isLoading: action.payload}; default: // Return existing state if no matching action type return state; } }
When using combineReducers, every sub-reducer must follow this rule. Double-check all the reducers you're combining—if even one misses the initial state or default case, you'll get that error.
Switch statements get unwieldy fast as you add more action types. Here are three practical solutions to clean this up:
Option 1: Use an Action Handler Map
Replace the switch statement with an object where keys are action types, and values are functions that update the state. This makes adding new actions as simple as adding a new key-value pair.
Example:
const initialState = { data: [], isLoading: false }; // Map action types to state update functions const actionHandlers = { 'UPDATE_DATA': (state, action) => ({...state, data: action.payload}), 'SET_LOADING': (state, action) => ({...state, isLoading: action.payload}), 'CLEAR_DATA': (state) => ({...state, data: []}) }; function appReducer(state = initialState, action) { // Get the handler for the current action type, or fall back to returning state const handler = actionHandlers[action.type]; return handler ? handler(state, action) : state; }
Option 2: Split Reducers by Feature (Your Existing combineReducers Approach)
You're already on the right track with combineReducers—this is the standard way to split large reducers into smaller, focused ones. Each sub-reducer handles only its own slice of the state, so switch statements stay small and manageable.
Example:
// user.reducer.ts const initialUserState = { name: '', isAuthenticated: false }; function userReducer(state = initialUserState, action) { switch(action.type) { case 'LOGIN': return {...state, name: action.payload.name, isAuthenticated: true}; case 'LOGOUT': return initialUserState; default: return state; } } // product.reducer.ts const initialProductState = { list: [], selectedId: null }; function productReducer(state = initialProductState, action) { switch(action.type) { case 'LOAD_PRODUCTS': return {...state, list: action.payload}; case 'SELECT_PRODUCT': return {...state, selectedId: action.payload}; default: return state; } } // Combine reducers into a root reducer import { combineReducers } from 'redux'; const rootReducer = combineReducers({ user: userReducer, products: productReducer });
Option 3: Use Redux Toolkit (Even for Angular 5)
Redux Toolkit is the official recommended way to write Redux logic, and it eliminates almost all boilerplate—including switch statements. It uses Immer under the hood, so you can write "mutating" code that gets converted to immutable updates automatically.
How to use it:
- Install it in your Angular 5 project:
npm install @reduxjs/toolkit
- Create a slice for each feature:
import { createSlice } from '@reduxjs/toolkit'; const initialUserState = { name: '', isAuthenticated: false }; const userSlice = createSlice({ name: 'user', initialState: initialUserState, reducers: { login: (state, action) => { // No spread operator needed—Immer handles immutability state.name = action.payload.name; state.isAuthenticated = true; }, logout: (state) => { state.name = ''; state.isAuthenticated = false; } } }); // Auto-generated action creators export const { login, logout } = userSlice.actions; // The reducer to include in combineReducers export default userSlice.reducer;
This approach keeps your code concise and eliminates the need for manual switch statements entirely.
内容的提问来源于stack exchange,提问作者AngularM

