如何实现含子项的递归数组拖拽重排?报错问题求助
Hey there! Let's break down your issues step by step and get that drag-and-drop working smoothly.
1. Fixing Stack Overflow & Duplicate Items in Add Logic
The root problem here is likely two-fold: state being duplicated across recursive components and mutating state directly instead of using immutable updates. Let's fix both:
a. Lift State to a Parent Component
Right now, you're initializing useReducer inside the TreeItems component, which means every recursive instance of TreeItems gets its own copy of the state. That's a recipe for chaos when trying to update the tree globally. Instead, move the state management to a top-level parent component:
// Parent component (holds the single source of truth) import { useReducer } from 'react'; import TreeItems from './TreeItems'; import Draggable from "../Hooks/Draggable"; const initialData = [ { id: 0, text: "test one" }, { id: 1, text: "test tow" }, { id: 2, text: "test three" }, { id: 3, text: "main", sub: [ { id: 4, text: "sub 1" }, { id: 5, text: "sub 2" }, ], }, ]; function TreeContainer() { const [state, dispatch] = useReducer(reducer, initialData); // Pass this handler to TreeItems to trigger reorder actions const handleReorder = (dragItem, targetParentId, insertIndex) => { dispatch({ type: 'REORDER_TREE', payload: { dragItem, targetParentId, insertIndex } }); }; return <TreeItems data={state} onReorder={handleReorder} />; } // Updated TreeItems (only handles rendering, not state) function TreeItems({ data, onReorder }) { return data.map((item) => ( <TreeItem key={item.id} // Use item.id instead of index! Indexes change when sorting nodeId={`${item.id}`} label={ <div draggable={true} {...Draggable(item, onReorder)}> {item.text} </div> } > {item.sub && <TreeItems data={item.sub} onReorder={onReorder} />} </TreeItem> )); }
b. Rewrite Reducer with Immutable Recursive Updates
Your add logic was probably causing stack overflow because you were mutating the original state or creating infinite recursion while traversing the tree. Use these helper functions to safely remove and insert items without mutation:
// Helper: Recursively remove the dragged item from the tree const removeDragItem = (items, dragItemId) => { return items .filter(item => item.id !== dragItemId) .map(item => ({ ...item, ...(item.sub && { sub: removeDragItem(item.sub, dragItemId) }) })); }; // Helper: Recursively find the target parent and insert the item const insertDragItem = (items, targetParentId, insertIndex, dragItem) => { // Insert at root if targetParentId is null if (targetParentId === null) { const newRoot = [...items]; newRoot.splice(insertIndex, 0, dragItem); return newRoot; } return items.map(item => { if (item.id === targetParentId) { // Insert into the parent's sub array const updatedSub = [...(item.sub || [])]; updatedSub.splice(insertIndex, 0, dragItem); return { ...item, sub: updatedSub }; } // Recurse into children if needed return item.sub ? { ...item, sub: insertDragItem(item.sub, targetParentId, insertIndex, dragItem) } : item; }); }; // Updated reducer function reducer(state, action) { switch (action.type) { case 'REORDER_TREE': { const { dragItem, targetParentId, insertIndex } = action.payload; // Step 1: Remove the dragged item from its original position const stateWithoutDrag = removeDragItem(state, dragItem.id); // Step 2: Insert it at the target position const newState = insertDragItem(stateWithoutDrag, targetParentId, insertIndex, dragItem); return newState; } default: return state; } }
c. Update Your Draggable Hook
Make sure your Draggable hook captures the necessary info when dragging ends: the dragged item, the target parent's ID, and the index to insert at. This ensures your handleReorder function has all the data it needs to update the state correctly.
2. Fixing CodeSandbox Material-UI Import Issues
If CodeSandbox isn't resolving Material-UI imports, try these steps:
- Use the correct package name: Newer Material-UI versions are under the
@muiscope. Make sure you're importing from@mui/material,@mui/lab(for TreeItem), etc., not the oldmaterial-uipackage. - Install dependencies explicitly: In CodeSandbox, go to the "Dependencies" tab and add
@mui/material,@mui/lab, and@emotion/react/@emotion/styled(required for MUI v5). - Verify import paths: Double-check that your imports look like this:
import TreeItem from '@mui/lab/TreeItem'; import TreeView from '@mui/lab/TreeView';
Quick Additional Tips
- Never use
indexas a key: When items are reordered, their indexes change, which causes React to reuse components incorrectly. Always use a unique, stable identifier likeitem.id. - Avoid state mutation: React relies on immutable state updates to detect changes. Always create new arrays/objects instead of modifying existing ones (that's what the spread operator
...is doing in the helpers above). - Debug with console logs: If you're still hitting issues, log the state before/after updates in your reducer to see where the duplicate items or infinite recursion is happening.
内容的提问来源于stack exchange,提问作者Ali Husham

