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

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()

错误原因

  1. forEach无法收集返回值:createNestedDOM中使用array.forEach循环,内部return的JSX元素不会被函数收集,函数最终返回undefined,导致父组件中{createNestedDOM(item.path)}渲染为空。
  2. 路径筛选逻辑不精准:child.path.startsWith(path + '/')会匹配所有后代节点,而非当前路径的直接子节点,结合全局pathArray会导致重复判断或遗漏节点。
  3. 全局变量污染:pathArray是全局变量,递归过程中会累积所有已处理路径,后续元素会因!pathArray.includes(item.path)被跳过,无法正常渲染。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:49:51