JavaScript中Fetch内部对象操作失效问题及uiReducer代码排查
Object.assign Isn't Updating State in uiReducer Hey there! I’ve dealt with this exact head-scratcher before—let’s break down why your state updates in the Fetch callback aren’t sticking, and how to fix it.
1. The Core Problem: Reducers Can’t Handle Async Code Directly
First off, Redux reducers are pure functions—they take the current state and an action, then return a new state immediately. They can’t handle asynchronous operations like Fetch requests, because by the time your Fetch callback runs, the reducer has already finished executing and returned the original state.
Here’s the play-by-play:
- You trigger the reducer, it starts running
- You kick off a Fetch request (async, so it runs in the background)
- The reducer finishes and returns the initial state before the Fetch resolves
- When the Fetch callback finally runs, modifying state with
Object.assigndoes nothing—because the reducer has already done its job, and that state reference isn’t connected to Redux’s store anymore
2. Did You Mutate State Instead of Returning a New Object?
Even if the async issue wasn’t there, using Object.assign incorrectly could be the culprit. Redux requires you to return a new state object, not mutate the existing one.
❌ Wrong (mutates the original state directly):
// Inside your reducer (bad practice + won't work for async) fetch('/api/data') .then(res => res.json()) .then(data => { Object.assign(state, { savedData: data }); // Mutates the existing state }); return state;
✅ Correct (returns a new state object—though this still won’t fix the async problem):
// Proper usage of Object.assign, but needs async handling const newState = Object.assign({}, state, { savedData: data }); return newState;
3. Is Your savedState from Local Storage Causing Reference Issues?
If you’re pulling savedState from local storage, make sure you’re not accidentally reusing a reference to the original stored object. When you parse JSON from local storage, it’s a new object, but if you assign it directly to state and mutate it later, that can cause unexpected behavior. Always initialize state with a copy:
// Correct way to load savedState from local storage const savedState = JSON.parse(localStorage.getItem('uiState')) || {}; // Initialize state with a fresh copy to avoid reference bugs const initialState = Object.assign({}, savedState);
The Fix: Move Async Logic to Action Creators
The proper way to handle async operations in Redux is to use a middleware like redux-thunk (the most common choice) to let you write action creators that return functions instead of plain objects. Here’s how to refactor your code:
Step 1: Set Up Redux-Thunk
Add it to your store configuration:
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import uiReducer from './uiReducer'; const store = createStore(uiReducer, applyMiddleware(thunk));
Step 2: Create an Async Action Creator
Move your Fetch logic into an action creator that dispatches actions when the request starts, succeeds, or fails:
export const fetchSavedData = () => { return (dispatch) => { // Optional: Dispatch a "loading" action to update UI state dispatch({ type: 'FETCH_SAVED_DATA_START' }); fetch('/api/data') .then(res => res.json()) .then(data => { // Dispatch a success action with the fetched data dispatch({ type: 'FETCH_SAVED_DATA_SUCCESS', payload: data }); // Update local storage if needed const currentState = JSON.parse(localStorage.getItem('uiState')) || {}; localStorage.setItem('uiState', JSON.stringify({ ...currentState, savedData: data })); }) .catch(error => { dispatch({ type: 'FETCH_SAVED_DATA_FAILURE', payload: error }); }); }; };
Step 3: Update Your Reducer to Handle the Action
Modify your uiReducer to listen for the success action and return a new state:
const savedState = JSON.parse(localStorage.getItem('uiState')) || {}; const initialState = Object.assign({}, savedState); const uiReducer = (state = initialState, action) => { switch (action.type) { case 'FETCH_SAVED_DATA_SUCCESS': // Return a new state object with the updated data return Object.assign({}, state, { savedData: action.payload }); // Handle other actions (like loading/failure) here default: return state; } }; export default uiReducer;
Final Quick Checks
- Make sure you’re calling the async action creator via
dispatch(fetchSavedData())from your component or app logic—never call it directly in the reducer. - Double-check that
Object.assignstarts with an empty object{}to ensure you’re creating a new state reference, not mutating the old one.
That should get your state updates working as expected!
内容的提问来源于stack exchange,提问作者MegRay

