如何在Redux Toolkit中更新嵌套对象?以Todo列表的Steps属性操作为例
First off, let's clear up your misunderstanding: Redux Toolkit absolutely lets you pass multiple values in an action.payload—you just need to wrap them in an object. You don't have to pass an entire Todo object every time you need to update a nested step. That's the key to cleaning up your code here.
The Optimal Approach: Use Immer's Mutable Syntax for Nested Updates
Redux Toolkit uses Immer under the hood, which lets you write "mutating" code that gets converted into immutable state updates automatically. This is perfect for updating nested properties like your steps—no need to copy entire Todo objects.
1. Add a Reducer for Toggling Step Completion
In your todoSlice.tsx, add a new case to your createSlice definition that targets the specific todo and step directly:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; // Import your Todo/Step types here const todoSlice = createSlice({ name: 'todo', initialState: { /* your initial state here */ }, reducers: { // ... your existing reducers (updateTodo, etc.) toggleStep: (state, action: PayloadAction<{ todoId: number; stepId: number }>) => { const { todoId, stepId } = action.payload; // Find the target todo const targetTodo = state.todos.find(todo => todo.id === todoId); if (targetTodo?.steps) { // Find the target step within that todo const targetStep = targetTodo.steps.find(step => step.id === stepId); if (targetStep) { // Immer handles the immutable update behind the scenes targetStep.completed = !targetStep.completed; } } }, }, }); export const { toggleStep, /* other exports */ } = todoSlice.actions; export default todoSlice.reducer;
2. Call the Reducer in Your Component
Now in your component, you can dispatch this action with just the IDs you need—no need to create a new Todo object:
const Rightbar: React.FC<rightbarProps> = ({ currentTodoId }) => { const dispatch = useDispatch(); const currentTodo = useSelector((state: RootState) => state.todo.todos.find(todo => todo.id === currentTodoId) ); const toggleStepCompletion = (stepId: number) => { if (currentTodoId) { dispatch(toggleStep({ todoId: currentTodoId, stepId })); } }; // ... rest of your component code };
For General Step Updates (e.g., Editing Title)
If you need to update other properties of a step (like its title), you can create a flexible reducer that accepts partial updates:
updateStep: (state, action: PayloadAction<{ todoId: number; stepId: number; updates: Partial<Step>; // Partial lets you pass any subset of Step properties }>) => { const { todoId, stepId, updates } = action.payload; const targetTodo = state.todos.find(todo => todo.id === todoId); if (targetTodo?.steps) { const targetStep = targetTodo.steps.find(step => step.id === stepId); if (targetStep) { // Merge the updates into the step object Object.assign(targetStep, updates); } } }
Then use it in your component like this:
const updateStepTitle = (stepId: number, newTitle: string) => { if (currentTodoId) { dispatch(updateStep({ todoId: currentTodoId, stepId, updates: { title: newTitle } })); } };
Why This Is Better Than Your Original Approach
- More efficient: You're only updating the specific step property you need, not replacing the entire Todo object. This reduces unnecessary re-renders and keeps state changes focused.
- Cleaner code: No need to manually copy the Todo and Steps arrays—Immer handles the immutable updates for you.
- Semantic actions: Your actions now clearly communicate what's being updated (e.g.,
toggleStepinstead of a genericupdateTodothat could mean anything).
Quick Note on Action Payloads
There's no rule that says action.payload has to be a single object or value. Using an object with named properties (like todoId and stepId) makes your code more readable and maintainable than passing multiple separate arguments.
内容的提问来源于stack exchange,提问作者Kai021195

