You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

修复后效果说明

  1. 输入内容点击Add后,新节点会正确添加到树形组件中,拖拽、展开/折叠、删除功能全部正常
  2. 输入框添加后自动清空,且有空值校验,避免无效提交
  3. 不会再出现控制台输出正常但组件失效的情况

内容的提问来源于stack exchange,提问作者Raj maxi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:04:45