React实现员工层级展开功能求助:基于Manager ID构建树形列表
解决方案
核心问题分析
你当前的代码无法实现层级渲染,主要原因是没有将扁平的员工数据转换为树形结构,且使用了全局的展开状态,无法支持多级独立展开。以下是完整的修复方案:
步骤1:将扁平数据转换为树形结构
首先需要把API返回的扁平数组转换为层级结构,每个员工节点包含下属列表:
const buildEmployeeTree = (employees) => { const employeeMap = {}; const rootEmployees = []; // 先将所有员工存入映射表,方便快速查找 employees.forEach(emp => { employeeMap[emp.id] = { ...emp, children: [] }; }); // 为每个员工找到对应的上级,构建层级关系 employees.forEach(emp => { if (emp.manager_id && employeeMap[emp.manager_id]) { employeeMap[emp.manager_id].children.push(employeeMap[emp.id]); } else { // 无manager_id的员工作为根节点 rootEmployees.push(employeeMap[emp.id]); } }); return rootEmployees; };
步骤2:创建递归的员工列表组件
每个员工项维护自己的展开状态,支持递归渲染下属:
const EmployeeItem = ({ employee }) => { const [open, setOpen] = useState(false); const handleClick = () => { setOpen(!open); }; return ( <> <ListItem> <ListItemButton onClick={handleClick}> <ListItemText primary={employee.name} /> {/* 仅当有下属时显示展开/收起图标 */} {employee.children.length > 0 && ( open ? <ExpandLess /> : <ExpandMore /> )} </ListItemButton> </ListItem> {/* 展开时渲染下属列表,添加缩进区分层级 */} <Collapse in={open} timeout="auto" unmountOnExit> <List component="div" disablePadding sx={{ pl: 4 }}> {employee.children.map(child => ( <EmployeeItem key={child.id} employee={child} /> ))} </List> </Collapse> </> ); };
步骤3:完整的主组件代码
整合数据处理和组件渲染逻辑:
import React, { useEffect, useState } from "react"; import { Collapse, List, ListItem, ListItemButton, ListItemText, } from "@mui/material"; import ExpandLess from "@mui/icons-material/ExpandLess"; import ExpandMore from "@mui/icons-material/ExpandMore"; const buildEmployeeTree = (employees) => { const employeeMap = {}; const rootEmployees = []; employees.forEach(emp => { employeeMap[emp.id] = { ...emp, children: [] }; }); employees.forEach(emp => { if (emp.manager_id && employeeMap[emp.manager_id]) { employeeMap[emp.manager_id].children.push(employeeMap[emp.id]); } else { rootEmployees.push(employeeMap[emp.id]); } }); return rootEmployees; }; const EmployeeItem = ({ employee }) => { const [open, setOpen] = useState(false); const handleClick = () => { setOpen(!open); }; return ( <> <ListItem> <ListItemButton onClick={handleClick}> <ListItemText primary={employee.name} /> {employee.children.length > 0 && ( open ? <ExpandLess /> : <ExpandMore /> )} </ListItemButton> </ListItem> <Collapse in={open} timeout="auto" unmountOnExit> <List component="div" disablePadding sx={{ pl: 4 }}> {employee.children.map(child => ( <EmployeeItem key={child.id} employee={child} /> ))} </List> </Collapse> </> ); }; export default function NewEmp() { const [employeeTree, setEmployeeTree] = useState([]); useEffect(() => { fetch( "https://opensheet.elk.sh/1gH5Kle-styszcHF2G0H8l1w1nDt1RhO9NHNCpHhKK0M/employees" ) .then((res) => res.json()) .then((data) => { const tree = buildEmployeeTree(data); setEmployeeTree(tree); }); }, []); return ( <List sx={{ width: "40%" }}> {employeeTree.map(emp => ( <EmployeeItem key={emp.id} employee={emp} /> ))} </List> ); }
关键修复说明
- 树形结构转换:解决扁平数据无法直接层级渲染的问题,每个员工节点包含下属列表。
- 独立展开状态:每个员工项维护自己的
open状态,支持多级独立展开/收起。 - 交互优化:仅当员工有下属时显示展开图标,子列表添加缩进直观区分层级。
内容的提问来源于stack exchange,提问作者subrath s
相关产品推荐
相关产品推荐

