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

如何为对象数组构建带复选框的MUI v5 TreeView(无父节点)

基于MUI v5 TreeView实现带复选框的树形菜单(数组数据源)

核心思路

你的数组数据源可以临时包装为一个虚拟根节点(仅用于适配TreeView的结构要求,不渲染到页面),同时自定义带复选框的TreeItem组件,并实现父子节点的勾选联动逻辑。

具体实现步骤

1. 临时包装数组为虚拟根结构

在渲染TreeView时,把你的数组数据包装成带children的虚拟根对象,TreeView直接渲染这个根的子节点,不显示根本身:

// 临时包装数组为虚拟根
const virtualRoot = {
  id: 'virtual-root',
  children: data // 你的原始数组数据
};

2. 自定义带复选框的TreeItem组件

用MUI的Checkbox和TreeItem组合,自定义一个带复选框的树形节点:

import { TreeItem, Checkbox } from '@mui/material';

const CheckboxTreeItem = ({ node, checkedIds, onToggle }) => {
  const isChecked = checkedIds.includes(node.id);
  // 判断是否半选中(子节点部分被勾选)
  const isIndeterminate = node.children?.some(child => checkedIds.includes(child.id)) && !isChecked;

  const handleToggle = () => {
    onToggle(node.id);
  };

  return (
    <TreeItem
      nodeId={node.id}
      label={
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Checkbox
            checked={isChecked}
            indeterminate={isIndeterminate}
            onChange={handleToggle}
            size="small"
          />
          {node.name}
        </div>
      }
    >
      {/* 递归渲染子节点 */}
      {node.children?.map(child => (
        <CheckboxTreeItem
          key={child.id}
          node={child}
          checkedIds={checkedIds}
          onToggle={onToggle}
        />
      ))}
    </TreeItem>
  );
};

3. 实现勾选联动逻辑

维护选中id的集合,处理单个节点勾选时的父子联动:

import { useState, useCallback } from 'react';
import { TreeView } from '@mui/x-tree-view';

const CheckboxTreeView = () => {
  const [checkedIds, setCheckedIds] = useState([]);
  
  // 递归获取所有子节点id
  const getAllChildIds = (node) => {
    let ids = [node.id];
    if (node.children) {
      node.children.forEach(child => {
        ids = [...ids, ...getAllChildIds(child)];
      });
    }
    return ids;
  };

  // 递归获取所有父节点id
  const getAllParentIds = (node, allNodes) => {
    let ids = [];
    const parent = allNodes.find(n => n.children?.some(c => c.id === node.id));
    if (parent) {
      ids.push(parent.id);
      ids = [...ids, ...getAllParentIds(parent, allNodes)];
    }
    return ids;
  };

  // 处理节点勾选/取消
  const handleToggle = useCallback((nodeId) => {
    // 扁平化所有节点,方便查找
    const flattenNodes = (nodes) => {
      let result = [];
      nodes.forEach(node => {
        result.push(node);
        if (node.children) {
          result = [...result, ...flattenNodes(node.children)];
        }
      });
      return result;
    };
    const allNodes = flattenNodes(data);
    const currentNode = allNodes.find(n => n.id === nodeId);

    let newCheckedIds;
    if (checkedIds.includes(nodeId)) {
      // 取消勾选:移除当前节点及所有子节点
      const childIds = getAllChildIds(currentNode);
      newCheckedIds = checkedIds.filter(id => !childIds.includes(id));
    } else {
      // 勾选:添加当前节点及所有子节点
      const childIds = getAllChildIds(currentNode);
      newCheckedIds = [...new Set([...checkedIds, ...childIds])];
    }

    // 更新父节点的勾选状态
    const parentIds = getAllParentIds(currentNode, allNodes);
    parentIds.forEach(parentId => {
      const parentNode = allNodes.find(n => n.id === parentId);
      const allChildChecked = parentNode.children.every(child => newCheckedIds.includes(child.id));
      if (allChildChecked) {
        if (!newCheckedIds.includes(parentId)) {
          newCheckedIds.push(parentId);
        }
      } else {
        newCheckedIds = newCheckedIds.filter(id => id !== parentId);
      }
    });

    setCheckedIds(newCheckedIds);
  }, [checkedIds, data]);

  return (
    <TreeView
      defaultExpanded={['3']} // 默认展开的节点id
      sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
    >
      {/* 渲染虚拟根的子节点 */}
      {virtualRoot.children.map(node => (
        <CheckboxTreeItem
          key={node.id}
          node={node}
          checkedIds={checkedIds}
          onToggle={handleToggle}
        />
      ))}
    </TreeView>
  );
};

export default CheckboxTreeView;

4. 使用组件

直接在页面中引入并使用这个自定义组件即可,你的原始数组数据可以直接传入使用。

关键说明

  • 虚拟根节点仅用于适配TreeView的结构要求,不会被渲染到页面,用户看到的就是你原始数组的一级节点。
  • 联动逻辑包含了父节点勾选/取消时自动同步所有子节点,以及子节点全部勾选/取消时自动同步父节点的半选/全选状态。
  • 可以根据需求调整Checkbox的样式、TreeView的尺寸等。

内容的提问来源于stack exchange,提问作者coderX

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 21:05:13