React Sortable Tree添加节点功能异常,寻求技术帮助
问题排查与修复方案
核心问题分析
你的React Sortable Tree组件功能失效,本质是错误的状态更新方式、未正确监听props变化导致的——直接修改state数组、用数组长度更新状态、渲染阶段重复执行添加逻辑,这些操作破坏了React的状态管理机制,最终让树形组件的数据结构彻底错乱。
1. 关键错误拆解(Treeview组件/Drag.js)
错误1:直接修改state并错误更新状态
你在组件顶层直接执行treeData.push(test),还尝试用push返回的数组长度(数字类型)去更新状态,这会把原本的树形数组直接变成数字,完全破坏组件需要的数据结构:
if (test.title != '') { var m = treeData.push(test) // setTreeData(m); // 这里m是数字,不是数组!直接让树形组件崩溃 }
React要求必须通过setState更新状态,且不能直接修改原state对象/数组,必须创建新的引用。
错误2:未监听props.info的变化
当父组件通过info传递新节点标题时,Treeview没有监听这个props的变化,所以新标题传过来后,组件不会触发添加节点的逻辑,也不会重新渲染。
错误3:渲染阶段重复执行添加逻辑
你把添加节点的逻辑放在组件顶层,每次组件渲染都会执行,这会导致重复添加节点,甚至初始化阶段就触发错误。
2. 次要问题(Add.js组件)
handleChange每次更新newItem时会覆盖整个对象,如果以后需要添加id、children等字段,会丢失原有属性,应该用展开运算符保留原对象:
const handleChange = (e) => { setNewItem(prev => ({ ...prev, [e.target.name]: e.target.value })); }
完整修复后的代码
修复后的Treeview组件(Drag.js)
import React, { useState, useEffect } from "react"; import 'react-sortable-tree/style.css'; import { removeNodeAtPath } from 'react-sortable-tree'; import SortableTree from 'react-sortable-tree'; import { toggleExpandedForAll } from 'react-sortable-tree'; import './styles.css' const Treeview = (props) => { // 把初始数据放在组件外,避免每次渲染重复创建 const initTreeData = [{ title: 'Data_1', children: [{ title: "Data_2" }] }, { title: 'Data_1' }, { title: 'Data_2' } ] const [treeData, setTreeData] = useState(initTreeData); // 监听props.info的变化,有新标题时添加节点 useEffect(() => { if (props.info && props.info.trim() !== '') { // 创建新节点,添加唯一id避免key警告 const newNode = { title: props.info, id: Date.now() }; // 创建新数组更新状态,绝不修改原state setTreeData(prevTree => [...prevTree, newNode]); } }, [props.info]); // 依赖props.info,仅当它变化时执行 // 展开/折叠逻辑简化 const toggleExpandAll = (expanded) => { setTreeData(toggleExpandedForAll({ treeData, expanded })); } // 删除节点逻辑优化 const removeNode = (rowInfo) => { setTreeData(prevTree => removeNodeAtPath({ treeData: prevTree, path: rowInfo.path, getNodeKey: ({ treeIndex }) => treeIndex, ignoreCollapsed: false, })); } return ( <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}> <div style={{ flex: '0 0 auto', padding: '0 15px' }}> <h3>Full Node Drag Theme</h3> <button onClick={() => toggleExpandAll(true)}>Expand All</button> <button onClick={() => toggleExpandAll(false)}>Collapse All</button> </div> <div style={{ flex: '1 0 50%', padding: '0 0 0 15px' }}> <SortableTree className="tree-dt" id="add_name" treeData={treeData} onChange={setTreeData} // 直接用setTreeData简化代码 generateNodeProps={rowInfo => ({ buttons: [ <button onClick={() => removeNode(rowInfo)}>X</button>, ], style: { height: '50px' } })} canDrag={({ node }) => !node.dragDisabled} getNodeKey={({ node }) => node.id || Date.now()} // 用唯一id避免key警告 /> </div> </div> ); } export default Treeview;
修复后的Add.js组件
import React, { useState } from 'react'; import { TextField } from '@fluentui/react/lib/TextField'; import { PrimaryButton, Stack } from '@fluentui/react'; import './styles.css' const TextFieldBasicExample = (props) => { const [newItem, setNewItem] = useState({ title: '' }); const handleChange = (e) => { // 用展开运算符保留原对象属性,避免覆盖 setNewItem(prev => ({ ...prev, [e.target.name]: e.target.value })); } const addData = (event) => { // 空值校验 if (newItem.title.trim() === '') { document.getElementById('error_name').textContent = '请输入节点内容'; return; } document.getElementById('error_name').textContent = ''; props.callback(newItem); // 添加后清空输入框 setNewItem({ title: '' }); } return ( <Stack horizontal> <Stack className="add-inp"> <TextField label="Add Item" placeholder="enter text" name="title" onChange={handleChange} value={newItem.title} // 绑定value,让输入框受控 /> <span id="error_name" style={{ color: 'red', fontSize: '12px' }}></span> <PrimaryButton text="Add" className="add-btn" onClick={addData} /> </Stack> </Stack> ); }; export default TextFieldBasicExample
父组件APP.js优化(可选)
添加清空逻辑,避免重复添加同一个节点:
import React, { useState } from "react"; import 'react-sortable-tree/style.css'; import TreeView from "./Drag&Drop"; import AddEdit from "./Add"; import './styles.css' const Tree = (props) => { const [info, setInfo] = useState(''); const data = (item) => { setInfo(item.title); // 延迟清空,确保Treeview能处理完新节点 setTimeout(() => setInfo(''), 100); } return ( <div> <div className="add-dt"> <div className="left-side"> <AddEdit callback={data} /> </div> <div className="right-side"> <TreeView info={info} /> </div> </div> </div> ); } export default Tree;
修复后效果说明
- 输入内容点击Add后,新节点会正确添加到树形组件中,拖拽、展开/折叠、删除功能全部正常
- 输入框添加后自动清空,且有空值校验,避免无效提交
- 不会再出现控制台输出正常但组件失效的情况
内容的提问来源于stack exchange,提问作者Raj maxi
相关产品推荐
相关产品推荐

