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
相关产品推荐
相关产品推荐

