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

React基于给定JSON结构实现可逐层展开的文件夹树组件

核心问题

原有实现无法达成预期效果的根因有两点:

  • 全局仅维护单个expand布尔状态,所有层级节点共用该状态,点击任意节点都会触发所有节点的显隐切换
  • 递归渲染逻辑未做分层控制,执行时会一次性递归渲染全部嵌套层级内容,没有为每个子节点实现独立的展开状态管理
正确实现方案

核心思路是拆分独立的递归树节点组件,每个节点独立维护自身的展开/收起状态,仅当节点被点击展开时,才渲染该节点对应的下一层子内容,递归执行该逻辑直到叶子属性节点:

import React, { useState } from 'react';

// 递归树节点组件,每个节点独立维护展开状态
const TreeNode = ({ nodeKey, nodeValue }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  // 判定可展开文件夹节点规则:值为非数组、非null的普通对象
  const isFolderNode = typeof nodeValue === 'object' && nodeValue !== null && !Array.isArray(nodeValue);
  
  // 叶子属性节点直接渲染内容,无展开交互
  if (!isFolderNode) {
    return (
      <li style={{ listStyle: 'none', padding: '2px 0' }}>
        <span style={{ color: '#666' }}>{nodeKey}: </span>
        <span>{Array.isArray(nodeValue) ? JSON.stringify(nodeValue) : nodeValue}</span>
      </li>
    );
  }

  // 文件夹节点渲染可点击标题,仅展开时渲染下一层子节点
  return (
    <li style={{ listStyle: 'none', padding: '2px 0' }}>
      <span
        onClick={() => setIsExpanded(!isExpanded)}
        style={{ cursor: 'pointer', userSelect: 'none' }}
      >
        {isExpanded ? '📂 ' : '📁 '}{nodeKey}
      </span>
      {isExpanded && (
        <ul style={{ margin: 0, paddingLeft: '20px' }}>
          {Object.entries(nodeValue).map(([childKey, childValue]) => (
            <TreeNode key={childKey} nodeKey={childKey} nodeValue={childValue} />
          ))}
        </ul>
      )}
    </li>
  );
};

const Testing = () => {
  const data = {
    "item1": {
      "item1.1": {
        "item1.1.1": {
          "item1.1.1.1": {
            "attr1": [],
            "attr2": "",
            "attr3": []
          }
        }
      },
      "item1.2": {
        "item1.2.1": {
          "item1.2.1.1": {
            "attr1": [],
            "attr2": "",
            "attr3": []
          }
        }
      }
    },
    "item2": {
      "item2.1": {
        "item2.1.1": {
          "item2.1.1.1": {
            "attr1": [],
            "attr2": "",
            "attr3": []
          }
        },
        "item2.1.2": {
          "item2.1.2.1": {
            "attr1": [],
            "attr2": "",
            "attr3": []
          },
          "item2.1.2.2": {
            "attr1": [],
            "attr2": "",
            "attr3": []
          }
        }
      }
    },
    "item3": {
      "item3.1": {
        "item3.1.1": {
          "item3.1.1.1": {
            "attr1": [],
            "attr2": "",
            "attr3": []
          }
        },
        "item3.1.2": {
          "attr1": [],
          "attr2": "",
          "attr3": []
        }
      }
    }
  };

  return (
    <div className="App" style={{ padding: 20 }}>
      <ul style={{ margin: 0, padding: 0 }}>
        {Object.entries(data).map(([rootKey, rootValue]) => (
          <TreeNode key={rootKey} nodeKey={rootKey} nodeValue={rootValue} />
        ))}
      </ul>
    </div>
  );
};

export default Testing;
效果说明
  • 初始状态所有节点默认收起,页面仅展示第一层根节点
  • 点击任意文件夹节点仅切换自身的展开状态,不会影响其他同级、父级节点
  • 自动识别叶子属性节点(数组、字符串类型值),到达最终属性层时直接渲染attr1/attr2/attr3的内容,无展开交互
  • 不受节点命名规则限制,任意结构的嵌套对象都可以按该逻辑递归渲染

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:39:35