React MUI Tree添加子节点后无法自动显示,需点击兄弟节点触发
React MUI Tree节点添加后不自动刷新的解决方案
问题根源
核心问题是直接修改了原始状态对象的引用,React无法检测到状态变化,导致Tree组件不会自动重渲染:
addChildHandler里直接操作全局的ROMItems对象,然后把同一个引用传给setV1Items,React认为状态未发生变化,因此不会更新组件。- 只有点击其他节点时,
RomTree内部的selectedTreeNode状态变化触发重渲染,才会显示新添加的节点。
修复方案
1. 保证状态更新的不可变性
在App.js的addChildHandler中,必须基于当前的v1Items状态创建新的对象副本,避免直接修改原始对象。通过展开运算符实现浅拷贝(当前层级结构适用),确保状态引用发生变化。
2. 优化组件传参逻辑
将完整的v1Items对象传递给RomTree,而非拆分单个属性,让renderTree可以直接使用完整节点数据,简化代码。
修正后的代码
App.js
import React, { useState } from 'react'; import logo from './logo.svg'; import './App.css'; import RomTree from './components/RomTree' import ChangeDriverForm from './components/ChangeDriverForm'; const ROMItems = { nodeId: 'root', label: 'New Epic', type: 'Epic', children: [{ nodeId:'child', label:'Child Epic', type: 'Epic', children: [{ nodeId:'child2', label:'Child Epic', type: 'Epic', children: []}] }] }; const App = () => { const [v1Items, setV1Items] = useState(ROMItems); const addChildHandler = (enteredChild) => { // 创建新的根节点副本,避免修改原始状态 const newRoot = { ...v1Items, children: [...v1Items.children, enteredChild] }; setV1Items(newRoot); }; return ( <div className="App"> <div> <ChangeDriverForm/> </div> <div>Rom Panel4</div> {/* 传递完整的树形数据对象 */} <RomTree onAddChild={addChildHandler} treeData={v1Items}></RomTree> </div> ); } export default App;
RomTree.js
import React, { useState } from "react"; import { TreeItem, TreeView } from '@mui/lab' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ChevronRightIcon from '@mui/icons-material/ChevronRight' function RomTree(props){ const [selectedTreeNode, setSelectedTreeNode] = useState('root'); function treeNodeSelect(event, nodeIds){ setSelectedTreeNode(nodeIds); }; function epicButtonHandler(){ const newEpicNode = { nodeId: Math.random().toString(), label: 'Child Epic', type: 'Epic', children: [] }; props.onAddChild(newEpicNode); }; function backlogButtonHandler(){ const newBacklogNode = { nodeId: Math.random().toString(), label: 'Child Backlog', type: 'Backlog', children: [] }; props.onAddChild(newBacklogNode); }; const renderTree = (nodes) => ( <TreeItem key={nodes.nodeId} nodeId={nodes.nodeId} label={nodes.label}> {Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null} </TreeItem> ); return( <div> <TreeView defaultCollapseIcon={<ExpandMoreIcon />} defaultExpandIcon={<ChevronRightIcon />} onNodeSelect={treeNodeSelect} aria-label="controlled" alignment="left" sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}> {/* 使用传入的完整树形数据渲染 */} {renderTree(props.treeData)} </TreeView> <button onClick={epicButtonHandler}>Add Epic</button> <button onClick={backlogButtonHandler}>Add Backlog</button> </div> ); } export default RomTree;
额外说明
- 现在点击添加按钮后,会创建全新的根节点对象,React检测到状态引用变化,会立即触发
RomTree重渲染,新节点直接显示。 - 如果需要给指定节点添加子节点(而非固定根节点),可以在
addChildHandler中传入目标节点的nodeId,递归查找并更新对应节点的children,同样要保持状态的不可变性。
内容的提问来源于stack exchange,提问作者motorhead9999
相关产品推荐
相关产品推荐

