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

React递归嵌套树形视图点击文件夹全折叠问题解决

React递归树形视图折叠异常问题

我在React中构建嵌套树形视图(包含文件夹和文件),需求是点击文件夹时仅折叠该文件夹的子节点。手动构建的视图功能正常,但递归构建的视图点击任意文件夹会导致整个树形视图折叠,问题出在display属性的设置位置。

手动构建的正常代码(SimpleTreeView.js)

import { useState } from "react";
import { IconFolder, IconFilePdf } from "./icons";

function ListFileItem({ name }) {
  return (
    <li>
      <IconFilePdf /> {name}
    </li>
  );
}
function ListItem({ name, children }) {
  const [isCollapsed, setIsCollapsed] = useState(false);
  return (
    <li>
      <div onClick={() => setIsCollapsed(!isCollapsed)}>
        <IconFolder /> {name}
      </div>
      <ul style={{ display: isCollapsed ? "none" : "block" }}>{children}</ul>
    </li>
  );
}

export default function SimpleTreeView() {
  return (
    <ul>
      <ListItem name="Folder 1">
        <ListFileItem name="File 1.1" />
        <ListFileItem name="File 1.2" />
      </ListItem>
      <ListItem name="Folder 2">
        <ListFileItem name="File 2.1" />
        <ListFileItem name="File 2.2" />
      </ListItem>
      <ListItem name="Folder 3">
        <ListFileItem name="File 3.1" />
        <ListItem name="Folder 3.2">
          <ListFileItem name="File 3.2.1" />
          <ListFileItem name="File 3.2.2" />
          <ListItem name="Folder 3.2.3">
            <ListFileItem name="File 3.2.3.1" />
            <ListFileItem name="File 3.2.3.2" />
          </ListItem>
        </ListItem>
      </ListItem>
    </ul>
  );
}

存在问题的递归代码(RecursiveTreeView.js)

import { useState } from "react";
import { IconFolder, IconFilePdf } from "./icons";

export default function RecursiveTreeView({ data }) {
  const [isCollapsed, setIsCollapsed] = useState(false);
  return (
    <ul style={{ display: isCollapsed ? "none" : "block" }}>
      {data.map((row, index) => (
        <li key={index}>
          {row.children ? (
            <div onClick={() => setIsCollapsed(!isCollapsed)}>
              <IconFolder /> {row.name}
            </div>
          ) : (
            <div>
              <IconFilePdf /> {row.name}
            </div>
          )}
          {row.children && <RecursiveTreeView data={row.children} />}
        </li>
      ))}
    </ul>
  );
}

问题原因

递归组件的isCollapsed状态定义在整个列表层级,并且把整个<ul>的display绑定到这个状态。这导致所有文件夹共享同一个折叠状态,点击任意文件夹都会切换全局状态,让整个树形视图被折叠/展开。而手动实现的ListItem是把状态放在单个文件夹内部,仅控制自身子列表,逻辑正确。

修复后的递归代码

import { useState } from "react";
import { IconFolder, IconFilePdf } from "./icons";

// 单个节点组件,独立管理折叠状态
function TreeItem({ node }) {
  const [isCollapsed, setIsCollapsed] = useState(false);

  return (
    <li>
      {node.children ? (
        <div onClick={() => setIsCollapsed(!isCollapsed)}>
          <IconFolder /> {node.name}
        </div>
      ) : (
        <div>
          <IconFilePdf /> {node.name}
        </div>
      )}
      {/* 仅控制当前节点的子列表显示 */}
      {node.children && (
        <ul style={{ display: isCollapsed ? "none" : "block" }}>
          {node.children.map((child, index) => (
            <TreeItem key={index} node={child} />
          ))}
        </ul>
      )}
    </li>
  );
}

export default function RecursiveTreeView({ data }) {
  return (
    <ul>
      {data.map((node, index) => (
        <TreeItem key={index} node={node} />
      ))}
    </ul>
  );
}

修复说明

  • 拆分出TreeItem组件,每个文件夹节点拥有独立的isCollapsed状态,互不干扰
  • 将子列表的display属性绑定到当前节点的状态,实现精准控制单个文件夹的折叠/展开
  • 根组件仅负责渲染顶层节点,不再管理折叠状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 11:23:19