React组件中嵌套DOM节点(文件夹树)创建失败问题排查
文件夹树嵌套DOM结构创建问题
我正尝试在React组件中使用<ul>和<li>标签创建类似文件夹树的嵌套DOM节点结构,该结构基于输入数组对象的"path"字段。执行代码后,"Hooks"的<ul>元素下没有嵌套元素,其子元素显示为undefined,请问我哪里出错了?
以下是我的代码:
let pathArray = [] let nested = []; let rootNode = []; let data = [ { "id": "bbc57df8b48fa010b34d3d586bcdb63e12fcc230", "name": "Hooks", "type": "tree", "path": "Hooks", "mode": "040000" }, { "id": "bbc57df8b48fa010b34d3d586bcdb63e12fcc230", "name": "ab1", "type": "tree", "path": "Hooks/ab1", "mode": "040000" }, { "id": "bbc57df8b48fa010b34d3d586bcdb63e12fcc230", "name": "ab2", "type": "tree", "path": "Hooks/ab2", "mode": "040000" }, { "id": "bbc57df8b48fa010b34d3d586bcdb63e12fcc230", "name": "bb2", "type": "tree", "path": "Hooks/ab1/bb2", "mode": "040000" }, { "id": "6d20bb276234ea4b97ee8e47a8efc5ae945cdc65", "name": "Routing", "type": "tree", "path": "Routing", "mode": "040000" }, { "id": "23602d99d0680243f32f07410b5c227f0d0c0331", "name": "L1.png", "type": "blob", "path": "Hooks/L1.png", "mode": "100644" }, { "id": "e89d96e5e23a7f0e7cb240b28dace2259328b3ee", "name": "ReadMe.md", "type": "blob", "path": "Hooks/ReadMe.md", "mode": "100644" }, { "id": "e89d96e5e23a7f0e7cb240b28dace2259328b3ee", "name": "ab1.md", "type": "blob", "path": "Hooks/ab1/ab1.md", "mode": "100644" }, { "id": "e89d96e5e23a7f0e7cb240b28dace2259328b3ee", "name": "ab2.md", "type": "blob", "path": "Hooks/ab2/ab2.md", "mode": "100644" }, { "id": "e89d96e5e23a7f0e7cb240b28dace2259328b3ee", "name": "bb2.md", "type": "blob", "path": "Hooks/ab1/bb2/bb2.md", "mode": "100644" }, { "id": "23602d99d0680243f32f07410b5c227f0d0c0331", "name": "L1.png", "type": "blob", "path": "L1.png", "mode": "100644" }, { "id": "b014be6ae81b88953520d2e43c94fa403a6384f8", "name": "ReadMe.md", "type": "blob", "path": "Routing/ReadMe.md", "mode": "100644" }, { "id": "77c1b823a94a7f3441c785ef27a072ce6db6b0e4", "name": "file.md", "type": "blob", "path": "file.md", "mode": "100644" }, { "id": "def1f0ba9051393b4128d0ecb8d35cb4780c936c", "name": "test.md", "type": "blob", "path": "test.md", "mode": "100644" } ]; const createNestedDOM = (path) => { console.log("path : " + path) let array = data.filter(child => child.path.startsWith(path + '/')) console.log("array : " + JSON.stringify(array)) console.log("current pathArray : " + JSON.stringify(pathArray)) // Loop through array elements array.forEach((item) => { console.log(" Current item : " + JSON.stringify(item)) if (!pathArray.includes(item.path)) { pathArray.push(item.path) if (item.type === "tree") { console.log("pushing UL path : " + item.path) return (<ul key={item.path} onClick={toggleDisplay} className={`${styles.boardPageList} my-2 py-2 px-1`}> <div className='d-flex justify-content-between'><span>{item.name}</span> <ChevronDown className='align-self-center' size={16} /></div> {createNestedDOM(item.path)} </ul>); } else { console.log("pushing LI path : " + item.path) return (<li key={item.path} value={item.name} onClick={(e) => { openBoard(e, item.name) }} className={`${styles.boardPageListItem}`}> {item.name} </li>) } } }); }; const createDirTree = () => { rootNode.forEach((item) => { let node = undefined; console.log("item from parent : " + JSON.stringify(item)) pathArray.push(item.path) if (item.type === "tree") { node = (<ul key={item.path} onClick={toggleDisplay} className={`${styles.boardPageList} my-2 py-2 px-1`}> <div className='d-flex justify-content-between'><span>{item.name}</span> <ChevronDown className='align-self-center' size={16} /></div> {createNestedDOM(item.path)} </ul>) } else { node = (<li key={item.path} value={item.name} onClick={(e) => { openBoard(e, item.name) }} className={`${styles.boardPageListItem} my-2 py-2`}> {item.name} </li>) } console.log(node) nested.push(node) }) } for (let index = 0; index < data.length; index++) { let obj = data[index] let arrPath = obj.path.split("/") if (arrPath.length == 1) { rootNode.push(obj) } } createDirTree()
错误原因
forEach无法收集返回值:createNestedDOM中使用array.forEach循环,内部return的JSX元素不会被函数收集,函数最终返回undefined,导致父组件中{createNestedDOM(item.path)}渲染为空。- 路径筛选逻辑不精准:
child.path.startsWith(path + '/')会匹配所有后代节点,而非当前路径的直接子节点,结合全局pathArray会导致重复判断或遗漏节点。 - 全局变量污染:
pathArray是全局变量,递归过程中会累积所有已处理路径,后续元素会因!pathArray.includes(item.path)被跳过,无法正常渲染。 - HTML结构不规范:
<ul>的直接子元素必须是<li>,原代码中直接在<ul>内放置<div>,不符合HTML规范,可能导致渲染异常。
修正后的代码
重构为纯递归函数,去掉全局变量,精准筛选直接子节点:
import { ChevronDown } from 'react-feather'; import styles from './your-styles.module.css'; // 替换为你的样式文件路径 const data = [/* 原数据保持不变 */]; // 递归渲染文件夹树 const renderFolderTree = (parentPath = '') => { // 筛选当前父路径下的直接子节点 const childItems = data.filter(item => { const pathSegments = item.path.split('/'); const parentSegments = parentPath.split('/'); // 根节点:路径只有一段 if (parentPath === '') { return pathSegments.length === 1; } // 直接子节点:路径层级比父路径多1,且前缀匹配父路径 return pathSegments.length === parentSegments.length + 1 && item.path.startsWith(parentPath + '/'); }); if (childItems.length === 0) return null; return ( <ul className={`${styles.boardPageList} my-2 py-2 px-1`}> {childItems.map(item => { if (item.type === 'tree') { // 文件夹节点:包含标题和子树 return ( <li key={item.path}> <div className='d-flex justify-content-between' onClick={toggleDisplay} > <span>{item.name}</span> <ChevronDown className='align-self-center' size={16} /> </div> {/* 递归渲染子文件夹 */} {renderFolderTree(item.path)} </li> ); } else { // 文件节点 return ( <li key={item.path} onClick={(e) => openBoard(e, item.name)} className={`${styles.boardPageListItem}`} > {item.name} </li> ); } })} </ul> ); }; // 组件示例 const FolderTreeComponent = () => { return renderFolderTree(); }; // 折叠/展开逻辑示例(需结合组件state实现) const toggleDisplay = (e) => { e.currentTarget.nextElementSibling.classList.toggle('hidden'); }; // 打开文件逻辑示例 const openBoard = (e, fileName) => { console.log('打开文件:', fileName); }; export default FolderTreeComponent;
关键改进点
- 移除全局变量:用纯函数递归,每个递归调用独立处理子节点,避免状态污染。
- 精准筛选直接子节点:通过路径层级判断,只渲染当前节点的直接子元素,避免嵌套混乱。
- 用
map替代forEach:map可以收集所有返回的JSX元素,组成数组返回给父组件,确保子节点正常渲染。 - 规范HTML结构:
<ul>内只放置<li>元素,文件夹标题和子树包裹在<li>中,符合HTML标准。
内容的提问来源于stack exchange,提问作者Shashank Kawle
相关产品推荐
相关产品推荐

