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

