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

React树形组件:父节点折叠时如何收起所有展开子节点?

React树形结构折叠修复方案

树形结构折叠异常

我基于React开发了XML转树形结构的功能,本地Visual Studio运行正常,但在线Sandbox环境中报错(不熟悉该环境报错逻辑)。当前树形结构可正常展开,但存在问题:父节点折叠时,已展开的子节点不会随之收起,需要修改代码解决该问题。


原代码

App.js

import React, { Component } from "react";
import ProductsTree from "./ProductsTreeView";
const initialState = {
  currentNode: {},
  data: ""
};
export class App extends Component {
  constructor(props) {
    super(props);
    this.state = initialState;
    this.setCurrentNode = this.setCurrentNode.bind(this);
  }
  setCurrentNode(node) {
    this.setState({ currentNode: node });
  }
  render() {
    return (
      <div>
        <ProductsTree setCurrentNode={this.setCurrentNode} />
      </div>
    );
  }
}
export default App;

ProductsTreeView.js

import React, { Component } from "react";
import axios from "axios";
import XMLParser from "react-xml-parser";
import { Link } from "react-router-dom";
import tree from "./tree.xml";
class ProductsTreeView extends Component {
  render() {
    return (
      <div id="TreeView">
        <TreeView setCurrentNode={this.props.setCurrentNode} />
      </div>
    );
  }
}

class Node {
  description = "n/a";
  id = -1;
  key_id = -1;
  linkpagename = "";
  isActive = false;
  nodes = [];
  constructor(description, id, key_id, linkpagename) {
    this.description = description;
    this.id = id;
    this.key_id = key_id;
    this.linkpagename = linkpagename;
  }

  static nodesFromXml(xml) {
    const map = (entity, nodes) => {
      const id = entity.attributes["id"];
      const key_id = entity.attributes["key-id"];
      const descriptionText =
        entity.children[
          entity.children.findIndex((child) => child.name === "description")
        ].value;
      const entities = entity.children.filter(
        (child) => child.name === "entity"
      );

      var linkPageName = entity.attributes["link-page-name"];
      linkPageName = linkPageName.replace(".aspx", "");
      const node = new Node(descriptionText, id, key_id, linkPageName);
      nodes.push(node);
      entities.forEach((entity) => map(entity, node.nodes));
    };
    const parsedData = new XMLParser().parseFromString(xml);
    const entities = parsedData.children.filter(
      (child) => child.name === "entity"
    );

    const nodes = [];
    entities.forEach((entity) => map(entity, nodes));
    return nodes;
  }
}

class TreeView extends React.Component {
  constructor(props) {
    super(props);
    this.state = { nodes: [] };
    this.toggleNode = this.toggleNode.bind(this);
  }
  componentDidMount() {
    axios
      .get(tree, { "Content-Type": "application/xml; charset=utf-8" })
      .then((response) =>
        this.setState({ nodes: Node.nodesFromXml(response.data) })
      )
      .catch((error) => console.error("Error:", error));
  }
  render() {
    const nodes = this.state.nodes
    return (
      <ul>
        {nodes.map((node) => (
          <TreeNode
            id={node.id}
            key={node.key_id}
            node={node}
            onToggle={this.toggleNode}
            setCurrentNode={this.props.setCurrentNode}
          />
        ))}
      </ul>
    );
  }
  toggleNode(node) {
    this.props.setCurrentNode(node);
    function _toggleNode(currentNode, node) {
      if (currentNode.id === node.id) {
        if (currentNode.key_id === node.key_id) {
          currentNode.isActive = !currentNode.isActive;
        }
      } else {
        currentNode.nodes.forEach((childNode) => _toggleNode(childNode, node));
      }
    }
    const nodes = this.state.nodes;
    nodes.forEach((currentNode) => _toggleNode(currentNode, node));
    this.setState((state) => (state.nodes = nodes));
  }
}
class TreeNode extends React.Component {
  render() {
    const node = this.props.node;
    const onToggle = this.props.onToggle;
    let activeChildren = null;
    if (node.isActive && node.nodes.length > 0) {
      activeChildren = (
        <ul>
          {node.nodes.map((node) => (
            <TreeNode
              id={node.id}
              key={node.key_id}
              node={node}
              onToggle={onToggle}
            />
          ))}
        </ul>
      );
    }

    return (
      <li
        id={node.id}
        linkpagename={node.linkpagename}
        key={node.key_id}
        onClick={(event) => {
          event.stopPropagation();
          onToggle(node);
        }}
      >
        <Link
          to={node.linkpagename}
          style={{ textDecoration: "none", color: "#000000" }}
        >
          {node.description}
        </Link>{" "}
        - {node.key_id} - {node.linkpagename}
        {activeChildren}
      </li>
    );
  }
}
export default ProductsTreeView;

tree.xml

<?xml version="1.0" standalone="yes"?>
<tree>
       <entity id="e11" key-id="1" link-page-name="Add_Category">
              <description>Service</description>
              <image>images/plus.gif</image>
              <imageNode>images/de.gif</imageNode>
              <imageOpen>images/minus.gif</imageOpen>
              <entity id="e248" key-id="48" link-page-name="Edit_Category">
                     <description>A_test1_test1</description>
                     <image>images/plus.gif</image>
                     <imageNode>images/de.gif</imageNode>
                     <imageOpen>images/minus.gif</imageOpen>
                     <entity id="e3717" key-id="717" link-page-name="Edit_Product">
                           <description>A_SubItem1</description>
                           <image>images/plus.gif</image>
                           <imageNode>images/de.gif</imageNode>
                           <imageOpen>images/minus.gif</imageOpen>
                           <entity id="e45546" key-id="5546" link-page-name="Edit_ProdTemplate">
                                  <description>A_Test_Template</description>
                                  <image>images/paper.gif</image>
                                  <imageOpen>images/paper.gif</imageOpen>
                           </entity>
                     </entity>
              </entity>
              <entity id="e247" key-id="47" link-page-name="Edit_Category">
                     <description>A_test6</description>
                     <image>images/plus.gif</image>
                     <imageNode>images/de.gif</imageNode>
                     <imageOpen>images/minus.gif</imageOpen>
                     <entity id="e3716" key-id="716" link-page-name="Edit_Product">
                           <description>A_Item</description>
                           <image>images/plus.gif</image>
                           <imageNode>images/de.gif</imageNode>
                           <imageOpen>images/minus.gif</imageOpen>
                           <entity id="e45545" key-id="5545" link-page-name="Edit_ProdTemplate">
                                  <description>temp1</description>
                                  <image>images/paper.gif</image>
                                  <imageOpen>images/paper.gif</imageOpen>
                           </entity>
                     </entity>
              </entity>
       </entity>

       <entity id="e12" key-id="2" link-page-name="Add_Category">
              <description>Sales</description>
              <image>images/plus.gif</image>
              <imageNode>images/de.gif</imageNode>
              <imageOpen>images/minus.gif</imageOpen>
              <entity id="e230" key-id="30" link-page-name="Edit_Category">
                     <description>Gift Cards</description>
                     <image>images/plus.gif</image>
                     <imageNode>images/de.gif</imageNode>
                     <imageOpen>images/minus.gif</imageOpen>
                     <entity id="e3421" key-id="421" link-page-name="Edit_Product">
                           <description>Sample Card</description>
                           <image>images/plus.gif</image>
                           <imageNode>images/de.gif</imageNode>
                           <imageOpen>images/minus.gif</imageOpen>
                           <entity id="e43308" key-id="3308" link-page-name="Edit_ProdTemplate">
                                  <description>greeting temp</description>
                                  <image>images/paper.gif</image>
                                  <imageOpen>images/paper.gif</imageOpen>
                           </entity>
                     </entity>
                     <entity id="e3422" key-id="422" link-page-name="Edit_Product">
                           <description>De Card</description>
                           <image>images/plus.gif</image>
                           <imageNode>images/de.gif</imageNode>
                           <imageOpen>images/minus.gif</imageOpen>
                           <entity id="e43309" key-id="3309" link-page-name="Edit_ProdTemplate">
                                  <description>NS Temp</description>
                                  <image>images/paper.gif</image>
                                  <imageOpen>images/paper.gif</imageOpen>
                           </entity>
                     </entity>
              </entity>
              <entity id="e215" key-id="15" link-page-name="Edit_Category">
                     <description>Chck</description>
                     <image>images/plus.gif</image>
                     <imageNode>images/de.gif</imageNode>
                     <imageOpen>images/minus.gif</imageOpen>
                     <entity id="e3671" key-id="671" link-page-name="Edit_Product">
                           <description>Add item</description>
                           <image>images/plus.gif</image>
                           <imageNode>images/de.gif</imageNode>
                           <imageOpen>images/minus.gif</imageOpen>
                           <entity id="e45438" key-id="5438" link-page-name="Edit_ProdTemplate">
                                  <description>Ahhhh</description>
                                  <image>images/paper.gif</image>
                                  <imageOpen>images/paper.gif</imageOpen>
                           </entity>
                     </entity>
                     <entity id="e3450" key-id="450" link-page-name="Edit_Product">
                           <description>Advtttt</description>
                           <image>images/plus.gif</image>
                           <imageNode>images/de.gif</imageNode>
                           <imageOpen>images/minus.gif</imageOpen>
                           <entity id="e43577" key-id="3577" link-page-name="Edit_ProdTemplate">
                                  <description>gggggg</description>
                                  <image>images/paper.gif</image>
                                  <imageOpen>images/paper.gif</imageOpen>
                           </entity>
                     </entity>
              </entity>
       </entity>
</tree>

修复方案

核心问题:当前仅切换了点击节点的isActive状态,未在父节点折叠时递归重置所有子节点的isActive状态。

1. 修改TreeView的toggleNode函数

替换原toggleNode方法,新增递归收起子节点的逻辑:

toggleNode(node) {
  this.props.setCurrentNode(node);
  
  // 递归重置所有子节点为折叠状态
  function collapseChildren(currentNode) {
    currentNode.isActive = false;
    currentNode.nodes.forEach(child => collapseChildren(child));
  }

  function _toggleNode(currentNode, node) {
    if (currentNode.id === node.id && currentNode.key_id === node.key_id) {
      currentNode.isActive = !currentNode.isActive;
      // 若当前节点变为折叠状态,同步收起所有子节点
      if (!currentNode.isActive) {
        currentNode.nodes.forEach(child => collapseChildren(child));
      }
    } else {
      currentNode.nodes.forEach((childNode) => _toggleNode(childNode, node));
    }
  }
  
  // 创建数组副本,遵循React状态不可变原则
  const nodes = [...this.state.nodes];
  nodes.forEach((currentNode) => _toggleNode(currentNode, node));
  this.setState({ nodes });
}

2. 补充TreeNode的setCurrentNode传递

在TreeNode组件的子节点渲染中添加setCurrentNode属性,确保所有节点点击都能更新父组件状态:

// 在TreeNode的map渲染中修改
<TreeNode
  id={node.id}
  key={node.key_id}
  node={node}
  onToggle={onToggle}
  setCurrentNode={this.props.setCurrentNode} // 新增该行
/>

说明

  1. 新增collapseChildren函数,递归将所有子节点的isActive设为false,保证父节点折叠时所有展开的子节点同步收起
  2. 修改setState写法,创建数组副本而非直接修改原数组,符合React状态更新的不可变原则
  3. 补充子节点的setCurrentNode传递,修复子节点点击无法更新父组件currentNode的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 05:55:23