小工具知识网

当前位置: 首页 > 行业知识 > 使用 React 和 MUI 创建多选 Checkbox 树组件

行业知识

使用 React 和 MUI 创建多选 Checkbox 树组件

2026-11-01 08:23:14 来源:互联网转载

在���篇博客中,我们将使用 React 和 MUI(Material-UI)库来创建一个多选 Checkbox 树组件。该组件可以用于展示树形结构的数据,并允许用户选择多个节点。

前提

在开始之前,确保你已经安装了以下依赖:

  • React
  • MUI(Material-UI)

    最终样式

    非全选状态

    全选状态

    思路

    我们的目标是创建一个多选 Checkbox 树组件,它可以接收树节点数据,并根据用户的选择返回选中的节点数据。为了实现这个目标,我们将按照以下步骤进行:

    1. 创建一个 React 函数组件 CheckBoxTree,它接收一个 data 属性作为树节点数据,并可选地接收一个 handleCheckData 属性作为回调函数,用于传递选中的节点数据。
    2. 在组件的状态中,创建一个 selected 数组,用于存储选中的节点的 id。
    3. 实现一个 onCheck 函数,用于处理节点 Checkbox 的点击事件。在该函数中,我们将根据用户的选择更新 selected 数组,并递归处理子节点的选中状态。
    4. 实现一个 renderTree 函数,用于递归渲染树节点。在该函数中,我们将根据节点的选中状态和子节点的数量来渲染 Checkbox 和节点名称。
    5. 使用 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({    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) {  // 按'_'分割节点id  const path = id.split('_');  // 累加生成父节点id  return 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,  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 (    nodes.id.id}      nodeId={nodes.id.id}      label={        (e) = e.stopPropagation()}          control={            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  {                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}      );};

    5. 渲染树形结构

    最后,我们使用 TreeView 和 TreeItem 组件来展示树形结构,并将树节点数据传递给 renderTree 函数进行渲染。代码如下:

    return (  true}      {data.map((item) => {      return renderTree(item);    })}  );

    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;}// 组件状态interface 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) {  // 按'_'分割节点id  const path = id.split('_');  // 累加生成父节点id  return 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({    selected: []  });  /**   * 点击节点Checkbox触发   * @param event 事件对象   * @param node 节点对象   * @param parentNodeName 父节点名称   */  const onCheck = (    event: React.ChangeEvent,    node: RegionType,    parentNodeName?: string  ) => {    // 获取Checkbox选中状态    const { checked } = event.target;    // 当前节点id    const 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) {      // 临时state      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}`)            )          };        }      }      // 更新state      setState(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);        }      });    }    // 当前节点id    const currentId = parentNodeName ?      `${parentNodeName}_${nodes.id.id}` :      nodes.id.id;    // 获取当前节点子节点总数    getNodeLength(nodes);    return (      nodes.id.id}        nodeId={nodes.id.id}        sx={{          '.MuiTreeItem-label': {            'maxWidth': '100%',            'overflow': 'hidden',            'wordBreak': 'break-all',            '.MuiFormControlLabel-label': {              pt: '2px'            }          }        }}        label={          (e) = e.stopPropagation()}            sx={{ alignItems: 'flex-start', mt: 1 }}            control={              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  {                  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}          );  };  /**   * 组件加载时触发,获取去重后的多选框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 (    true}          {data.map((item) => {        return renderTree(item);      })}      );}

    总结

    通过以上步骤,我们成功地创建了一个多选 Checkbox 树组件。该组件可以接收树节点数据,并根据用户的选择返回选中的节点数据。我们使用了 React 和 MUI(Material-UI)库来实现这个功能,并按照前提、思路和步骤的顺序进行了解析和实现。

    希望本篇博客对你理解如何使用 React 和 MUI 创建多选 Checkbox 树组件有所帮助!如果你有任何问题或建议,请随时留言。谢谢阅读!

上一篇:怎么查看服务器cpu是否正常?

下一篇:【MySQL数据库】详解数据库审核工具SQLE的部署及接口调用