Redux技术问题:如何基于已有状态创建新状态?
Awesome that you’ve nailed the core Redux building blocks—action creators, actions, reducers, dispatchers! Let’s break down how to derive new state from existing state using your names/hobbies scenario. There are two common approaches here: calculating derived state directly in your reducer, or using selectors to compute it on-demand (the latter is usually preferred to avoid redundant state in your store).
1. Calculating Derived State in Your Reducer
If you need to persist the derived state in your store (rare, but useful for specific cases), you can compute it directly in your reducer by accessing other parts of the current state.
First, let’s formalize your existing setup as you described:
// actionTypes.js export const NAMES = 'NAMES'; export const HOBBIES = 'HOBBIES'; // actionCreators.js export const addName = (name) => ({ type: NAMES, payload: name }); export const addHobby = (hobby) => ({ type: HOBBIES, payload: hobby });
Now, in your reducer, let’s say we want to maintain a combinedItems state that merges both names and hobbies. We’ll update this every time a name or hobby is added, using the existing state values:
// reducer.js import { NAMES, HOBBIES } from './actionTypes'; const initialState = { names: [], hobbies: [], combinedItems: [] // Our derived state }; const rootReducer = (state = initialState, action) => { switch (action.type) { case NAMES: const updatedNames = [...state.names, action.payload]; // Derive new combinedItems using updated names + existing hobbies const newCombinedItems = [...updatedNames, ...state.hobbies]; return { ...state, names: updatedNames, combinedItems: newCombinedItems }; case HOBBIES: const updatedHobbies = [...state.hobbies, action.payload]; // Derive new combinedItems using existing names + updated hobbies const newCombinedItems2 = [...state.names, ...updatedHobbies]; return { ...state, hobbies: updatedHobbies, combinedItems: newCombinedItems2 }; default: // Initialize combinedItems on first load return { ...state, combinedItems: [...state.names, ...state.hobbies] }; } }; export default rootReducer;
In your App.js, you can dispatch actions as usual, and the derived combinedItems state will update automatically:
// App.js import { useDispatch, useSelector } from 'react-redux'; import { addName, addHobby } from './actionCreators'; function App() { const dispatch = useDispatch(); const combinedItems = useSelector(state => state.combinedItems); return ( <div> <button onClick={() => dispatch(addName('Alice'))}>Add Name</button> <button onClick={() => dispatch(addHobby('Reading'))}>Add Hobby</button> <h3>Combined Items:</h3> <ul> {combinedItems.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); } export default App;
2. Using Selectors for On-Demand Derived State
Most of the time, you don’t need to store derived state in your store—you can compute it when you need it using selectors. Selectors are pure functions that take the full Redux state and return the derived data. This keeps your store lean and avoids redundant state updates.
Let’s create a selector that combines names and hobbies, or a more complex one that associates names with their hobbies (assuming your data has user IDs):
// selectors.js // Simple selector to combine names and hobbies export const getCombinedItems = (state) => { return [...state.names, ...state.hobbies]; }; // Complex selector: Associate each name with their hobbies (structured data example) export const getUserHobbies = (state) => { return state.names.map(name => ({ ...name, hobbies: state.hobbies.filter(hobby => hobby.userId === name.id) })); };
Now, use this selector in your component to get the derived state without modifying your reducer:
// App.js import { useDispatch, useSelector } from 'react-redux'; import { addName, addHobby } from './actionCreators'; import { getUserHobbies } from './selectors'; function App() { const dispatch = useDispatch(); const userHobbies = useSelector(getUserHobbies); return ( <div> <button onClick={() => dispatch(addName({ id: 1, name: 'Alice' }))}>Add Alice</button> <button onClick={() => dispatch(addHobby({ id: 1, name: 'Reading', userId: 1 }))}>Add Reading</button> <h3>User Hobbies:</h3> <ul> {userHobbies.map(user => ( <li key={user.id}> {user.name}: {user.hobbies.map(h => h.name).join(', ')} </li> ))} </ul> </div> ); } export default App;
Quick Tips
- For performance, use memoized selectors (via libraries like
reselect) to avoid recalculating derived state when unrelated parts of the store change. - Sticking with selectors keeps your store focused on raw source data, ensuring a single source of truth for your app’s state.
内容的提问来源于stack exchange,提问作者Peter

