使用 React 和 MUI 创建多选 Checkbox 树组件
在本篇博客中,我们将使用 React 和 MUI(Material-UI)库来创建一个多选 Checkbox 树组件。该组件可以用于展示树形结构的数据,并允许用户选择多个节点。
前提
在开始之前,确保你已经安装了以下依赖:
- React
- MUI(Material-UI)
最终样式
非全选状态

全选状态

思路
我们的目标是创建一个多选 Checkbox 树组件,它可以接收树节点数据,并根据用户的选择返回选中的节点数据。为了实现这个目标,我们将按照以下步骤进行:
- 创建一个 React 函数组件
CheckBoxTree,它接收一个data属性作为树节点数据,并可选地接收一个handleCheckData属性作为回调函数,用于传递选中的节点数据。 - 在组件的状态中,创建一个
selected数组,用于存储选中的节点的 id。 - 实现一个
onCheck函数,用于处理节点 Checkbox 的点击事件。在该函数中,我们将根据用户的选择更新selected数组,并递归处理子节点的选中状态。 - 实现一个
renderTree函数,用于递归渲染树节点。在该函数中,我们将根据节点的选中状态和子节点的数量来渲染 Checkbox 和节点名称。 - 使用
TreeView和TreeItem组件来展示树形结构,并将树节点数据传递给renderTree函数进行渲染。
步骤
下面是实现多选 Checkbox 树组件的详细步骤:
1. 创建 React 函数组件
首先,我们需要创建一个 React 函数组件 CheckBoxTree,并定义它的属性和状态。代码如下:
import React from 'react';interface CheckboxTreeState {selected: string[];
}interface CheckBoxTreeProps {data: RegionType[]; //起码要包含childre,name和parentId,handleCheckData?: (data: string[]) => void;
}export default function CheckBoxTree(props: CheckBoxTreeProps) {const { data, handleCheckData } = props;const [state, setState] = React.useState<CheckboxTreeState>({selected: []});// ...
}
2. 分割父节点
接下来,我们定义了splitNodeId函数,用于将节点id拆分为所有父节点id。它接受一个节点id字符串,格式为'1_2_3',并返回一个父节点id数组,例如['1_2', '1']。3表示的是当前节点。
/*** 拆分节点id为所有父节点id* @param id 节点id,格式为'1_2_3'* @returns 父节点id数组,如['1_2', '1']*/
function splitNodeId(id: string) {// 按'_'分割节点idconst path = id.split('_');// 累加生成父节点idreturn path.reduce((result: string[], current) => {// 拼接'_'和当前节点result.push(`${result.at(-1) ? result.at(-1) + '_' : ''}${current}`);return result;}, []);
}
3. 实现节点 Checkbox 的点击事件处理函数
接下来,我们需要实现一个 onCheck 函数,用于处理节点 Checkbox 的点击事件。在该函数中,我们将根据用户的选择更新 selected 数组,并递归处理子节点的选中状态。代码如下:
const onCheck = (event: React.ChangeEvent<HTMLInputElement>,node: RegionType,parentNodeName?: string
) => {const { checked } = event.target;const currentId = parentNodeName ?`${parentNodeName}_${node.id.id}` :node.id.id;const parentAreaName = splitNodeId(currentId);if (checked) {setState((prevState) => ({...prevState,selected: Array.from(new Set([...prevState.selected, ...parentAreaName]))}));if (node.children && node.children.length > 0) {node.children.forEach((item) => {onCheck(event, item, currentId);});}} else if (!checked) {let tempState = { ...state };for (let index = parentAreaName.length - 1; index >= 0; index--) {const element = parentAreaName[index];if (tempState.selected.filter((id) => id.startsWith(`${element}_`)).length === 0) {tempState = {...tempState,selected: tempState.selected.filter((id) => id !== element)};}if (tempState.selected.filter((id) => id.startsWith(`${currentId}_`)).length !== 0) {tempState = {...tempState,selected: tempState.selected.filter((id) =>!id.startsWith(`${currentId}_`) &&!id.startsWith(`${currentId}`))};}}setState(tempState);}
};
4. 实现递归渲染树节点的函数
然后,我们需要实现一个 renderTree 函数,用于递归渲染树节点。在该函数中,我们将根据节点的选中状态和子节点的数量来渲染 Checkbox 和节点名称。代码如下:
const renderTree = (nodes: RegionType, parentNodeName?: string) => {let currentLength = 0;function getNodeLength(currentNodes: RegionType) {currentNodes.children?.forEach((node) => {currentLength++;if (node.children) {getNodeLength(node);}});}const currentId = parentNodeName ?`${parentNodeName}_${nodes.id.id}` :nodes.id.id;getNodeLength(nodes);return (<TreeItemkey={nodes.id.id}nodeId={nodes.id.id}label={<FormControlLabelonClick={(e) => e.stopPropagation()}control={<Checkboxname={nodes.name}checked={nodes.children &&nodes.children.length &&state.selected.filter((id) =>id.startsWith(`${currentId}_`)).length === currentLength ||state.selected.some((id) => id === currentId)}indeterminate={nodes.children &&nodes.children.length > 0 &&state.selected.some((id) => id.startsWith(`${currentId}_`)) &&state.selected.filter((id) => id.startsWith(`${currentId}_`)).length < currentLength}onChange={(e) => {e.stopPropagation();onCheck(e, nodes, parentNodeName);}}onClick={(e) => e.stopPropagation()}/>}label={nodes.name}/>}>{Array.isArray(nodes.children) ?nodes.children.map((node) => renderTree(node, currentId)) :null}</TreeItem>);
};
5. 渲染树形结构
最后,我们使用 TreeView 和 TreeItem 组件来展示树形结构,并将树节点数据传递给 renderTree 函数进行渲染。代码如下:
return (<TreeViewaria-label="checkbox tree"defaultCollapseIcon={<ExpandMore />}defaultExpandIcon={<ChevronRight />}disableSelection={true}>{data.map((item) => {return renderTree(item);})}</TreeView>
);
6. 完整代码
import { ChevronRight, ExpandMore } from '@mui/icons-material';
import { TreeItem, TreeView } from '@mui/lab';
import { Checkbox, FormControlLabel } from '@mui/material';
import React from 'react';export interface RegionType {abbreviation: string;children?: RegionType[];createdTime: number;id: EntityData;level: number;name: string;nameCn: string;parentId: string;sort: number;status: boolean;
}// 组件状态
int
erface CheckboxTreeState {// 选中节点id数组selected: string[];
}// 组件属性
interface CheckBoxTreeProps {// 树节点数据data: RegionType[];// 向外传递选择框数据,handleCheckData?: (data: string[]) => void;
}/*** 拆分节点id为所有父节点id* @param id 节点id,格式为'1_2_3'* @returns 父节点id数组,如['1_2', '1']*/
function splitNodeId(id: string) {// 按'_'分割节点idconst path = id.split('_');// 累加生成父节点idreturn path.reduce((result: string[], current) => {// 拼接'_'和当前节点result.push(`${result.at(-1) ? result.at(-1) + '_' : ''}${current}`);return result;}, []);
}/*** 多选Checkbox树组件* @param props 组件属性* @returns JSX组件*/
export default function CheckBoxTree(props: CheckBoxTreeProps) {// 获取树节点数据const { data, handleCheckData } = props;// 组件状态:选中节点id数组const [state, setState] = React.useState<CheckboxTreeState>({selected: []});/*** 点击节点Checkbox触发* @param event 事件对象* @param node 节点对象* @param parentNodeName 父节点名称*/const onCheck = (event: React.ChangeEvent<HTMLInputElement>,node: RegionType,parentNodeName?: string) => {// 获取Checkbox选中状态const { checked } = event.target;// 当前节点idconst currentId = parentNodeName ?`${parentNodeName}_${node.id.id}` :node.id.id;// 父节点id数组const parentAreaName = splitNodeId(currentId);// 选中状态:选中当前节点和父节点if (checked) {setState((prevState) => ({...prevState,//使用Set对selected数组去重selected: Array.from(new Set([...prevState.selected, ...parentAreaName]))}));// 若有子节点,递归选中if (node.children && node.children.length > 0) {node.children.forEach((item) => {onCheck(event, item, currentId);});}} else if (!checked) {// 临时statelet tempState = { ...state };// 逆序遍历,进行选中状态更新for (let index = parentAreaName.length - 1; index >= 0; index--) {const element = parentAreaName[index];// 若父区域已无选中节点,取消选中父区域if (tempState.selected.filter((id) => id.startsWith(`${element}_`)).length === 0) {tempState = {...tempState,selected: tempState.selected.filter((id) => id !== element)};}// 取消选中当前区域if (tempState.selected.filter((id) => id.startsWith(`${currentId}_`)).length !== 0) {tempState = {...tempState,selected: tempState.selected.filter((id) =>!id.startsWith(`${currentId}_`) &&!id.startsWith(`${currentId}`))};}}// 更新statesetState(tempState);}};/*** 递归渲染树节点* @param nodes 树节点数组* @param parentNodeName 父节点名称* @returns JSX组件*/const renderTree = (nodes: RegionType, parentNodeName?: string) => {// 子节点总数let currentLength = 0;/*** 获取子节点总数* @param currentNodes 当前节点*/function getNodeLength(currentNodes: RegionType) {currentNodes.children?.forEach((node) => {currentLength++;if (node.children) {getNodeLength(node);}});}// 当前节点idconst currentId = parentNodeName ?`${parentNodeName}_${nodes.id.id}` :nodes.id.id;// 获取当前节点子节点总数getNodeLength(nodes);return (<TreeItemkey={nodes.id.id}nodeId={nodes.id.id}sx={{'.MuiTreeItem-label': {'maxWidth': '100%','overflow': 'hidden','wordBreak': 'break-all','.MuiFormControlLabel-label': {pt: '2px'}}}}label={<FormControlLabelonClick={(e) => e.stopPropagation()}sx={{ alignItems: 'flex-start', mt: 1 }}control={<Checkboxname={nodes.name}sx={{ pt: 0 }}checked={// 若有子节点,判断子节点是否全部选中// 或节点自身是否选中nodes.children &&nodes.children.length &&state.selected.filter((id) =>id.startsWith(`${currentId}_`)).length === currentLength ||state.selected.some((id) => id === currentId)}indeterminate={// 子节点存在选中与非选中状态nodes.children &&nodes.children.length > 0 &&state.selected.some((id) => id.startsWith(`${currentId}_`)) &&state.selected.filter((id) => id.startsWith(`${currentId}_`)).length < currentLength}onChange={(e) => {e.stopPropagation();onCheck(e, nodes, parentNodeName);}}onClick={(e) => e.stopPropagation()}/>}label={nodes.name}/>}>{Array.isArray(nodes.children) ?nodes.children.map((node) => renderTree(node, currentId)) :null}</TreeItem>);};/*** 组件加载时触发,获取去重后的多选框id列表*/React.useEffect(() => {// state.selected拆分数组并合并,返回成一个数组,如果需要去重后的值,可以使用Array.from(new set)const checkBoxList = state.selected.flatMap((item) => item.split('_'));// 因为是通过parent id来绑定子元素,所以下面的元素是只返回最后的子元素const checkTransferList = checkBoxList.filter((value) => checkBoxList.indexOf(value) === checkBoxList.lastIndexOf(value));// 从多选值数组中生成集合Set,再使用Array.from转换为数组if (handleCheckData) {handleCheckData(checkTransferList);}}, [state]);React.useEffect(() => {if (data.length) {setState({ selected: [] });}}, [data]);return (<TreeViewaria-label="checkbox tree"defaultCollapseIcon={<ExpandMore />}defaultExpandIcon={<ChevronRight />}disableSelection={true}>{data.map((item) => {return renderTree(item);})}</TreeView>);
}
总结
通过以上步骤,我们成功地创建了一个多选 Checkbox 树组件。该组件可以接收树节点数据,并根据用户的选择返回选中的节点数据。我们使用了 React 和 MUI(Material-UI)库来实现这个功能,并按照前提、思路和步骤的顺序进行了解析和实现。
希望本篇博客对你理解如何使用 React 和 MUI 创建多选 Checkbox 树组件有所帮助!如果你有任何问题或建议,请随时留言。谢谢阅读!
相关文章:
使用 React 和 MUI 创建多选 Checkbox 树组件
在本篇博客中,我们将使用 React 和 MUI(Material-UI)库来创建一个多选 Checkbox 树组件。该组件可以用于展示树形结构的数据,并允许用户选择多个节点。 前提 在开始之前,确保你已经安装了以下依赖: Reac…...
vue3里面使用el-image-vie出现图片预览导致页面卡顿停止加载问题
需求:我们在使用element-plus组件里面的图片预览时候,通过点击按钮来实现图片预览的效果。在开发过程中我们会遇到图片预览的时候出现卡顿出不来,导致当前的页面停止加载了。 具体思路如下: 我们需要添加:preview-teleported“t…...
Leetcoder Day26| 回溯part06:总结+三道hard题
332.重新安排行程 给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必…...
浅谈 Linux 网络编程 - 网络字节序
文章目录 前言核心知识关于 小端法关于 大端法网络字节序的转换 函数 前言 在进行 socket 网络编程时,会用到字节流的转换函数、例如 inet_pton、htons 等,那么为什么要用到这些函数呢,本篇主要就是对这部分进行介绍。 核心知识 重点需要记…...
Nginx网络服务六-----IP透传、调度算法和负载均衡
1.实现反向代理客户端 IP 透传 就是在日志里面加上一个变量 Module ngx_http_proxy_module [rootcentos8 ~]# cat /apps/nginx/conf/conf.d/pc.conf server { listen 80; server_name www.kgc.org; location / { index index.html index.php; root /data/nginx/html/p…...
【Linux进程】进程状态---进程僵尸与孤儿
📙 作者简介 :RO-BERRY 📗 学习方向:致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持 目录 1.进程排队2.进程状态…...
MySQL数据库基础知识总结(适合小白入门使用)一
文章目录 一 数据库数据表的创建等基本操作二 数据类型的测试三 完整性约束条件四 数据表结构的相关操作五 对表中数据的操作六 表达式与查询七 高级的查询功能 一 数据库数据表的创建等基本操作 #注释内容(与python很像) -- 也为注释内容 -- 创建一个数…...
历史新知网:寄快递寄个电脑显示器要多少钱?
以下文字信息由(新史知识网)编辑整理发布。 让我们赶紧来看看吧! 问题1:快递寄电脑显示器要多少钱? 此物有多重? 顺丰寄就可以了,但是必须是原包装的,不然不好寄。 问题2࿱…...
在两台CentOS 7服务器上部署MinIO集群。
环境说明: 2台Centos7服务器 IP地址分别为172.16.1.9和172.16.1.10 1. 创建minio用户和目录 在两台服务器上执行以下命令: sudo useradd -m -d /app/minio minio sudo mkdir -p /app/minioData sudo mkdir -p /app/minio/logs sudo chown -R mini…...
【计算机网络】深度学习使用应用层的HTTP协议
💓 博客主页:从零开始的-CodeNinja之路 ⏩ 收录文章:【计算机网络】深度学习使用应用层的HTTP协议 🎉欢迎大家点赞👍评论📝收藏⭐文章 文章目录 一:HTTP是什么二:HTTP请求1.HTTP请求的组成2.HTTP请求的方法…...
Ubuntu18.04 系统上配置并运行SuperGluePretrainedNetwork(仅使用CPU)
SuperGlue是Magic Leap在CVPR 2020上展示的研究项目,它是一个图神经网络(Graph Neural Network)和最优匹配层(Optimal Matching layer)的结合,训练用于对两组稀疏图像特征进行匹配。这个项目提供了PyTorch代…...
协议-http协议-基础概念01-发展历程-http组成-http是什么-相关的应用-相关的协议
发展历程-http组成-http是什么-相关的应用-相关的协议 参考来源: 极客时间-透视HTTP协议(作者:罗剑锋); 01-HTTP的发展历程 1989 年,任职于欧洲核子研究中心(CERN)的蒂姆伯纳斯 - 李(Tim Ber…...
UI学习-学习内容
教程网址1:UI 新手如何从设计规范中提升自己 推荐一下高质量的设计规范 满屏干货 语雀 B站地址1:新像素 UI 新手如何从设计规范中提升自己 推荐一下高质量的设计规范 满屏干货 UI设计培训_哔哩哔哩_bilibili 教程地址2:UI 新手成长经验分享…...
Flink CDC 提取记录变更时间作为事件时间和 Hudi 表的 precombine.field 以及1970-01-01 取值问题
博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,…...
【网络安全】网络安全意识教育实用指南
随着科技的不断发展和数字世界的变革,我们不仅从中获得前所未有的力量,也同时面临着前所未有的风险挑战。多数CISO(首席信息安全官)时刻致力于协助企业抵御各种安全威胁。在“武器库”中有一件珍贵的法宝:网络安全意识…...
wordpress模板购买网站推荐
简站wordpress主题 老牌wordpress开发团队,开发过数百款wordpress主题,作品是最好的简历,靠作品说话,看作品喜欢不喜欢就可以了。 https://www.jianzhanpress.com WP模板牛 免费wordpress下载网站,上面有上百款免费…...
LeetCode 刷题 [C++] 第240题.搜索二维矩阵 II
题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 题目分析 通过分析矩阵的特点发现,其左下角和右上角可以看作一个“二叉搜索树的根节…...
HP笔记本电脑如何恢复出厂设置?这里提供几种方法
要恢复出厂设置Windows 11或10的HP笔记本电脑,你可以使用操作系统的标准方法。如果你运行的是早期版本,你可以使用HP提供的单独程序清除计算机并重新安装操作系统。 恢复出厂设置运行Windows 11的HP笔记本电脑 所有Windows 11计算机都有一个名为“重置此电脑”的功能,可…...
Elasticsearch:了解人工智能搜索算法
作者:来自 Elastic Jessica Taylor, Aditya Tripathi 人工智能工具无处不在,其原因并不神秘。 他们可以执行各种各样的任务并找到许多日常问题的解决方案。 但这些应用程序的好坏取决于它们的人工智能搜索算法。 简单来说,人工智能搜索算法是…...
(HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
一、电路接法 电路接法参照江科大视频。 二、相关代码及文件 说明:代码采用hal库,通过修改江科大代码实现。仅OLED.c文件关于引脚定义作了hal库修改,并将宏定义OLED_W_SCL(x)、OLED_W_SDA(x)作了相关修改。 1、OLED.c void OLED_I2C_Init(voi…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
