最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解如何使用React和MUI創(chuàng)建多選Checkbox樹組件

 更新時(shí)間:2024年01月09日 10:19:32   作者:Evan不懂前端  
這篇文章主要為大家詳細(xì)介紹了如何使用 React 和 MUI(Material-UI)庫來創(chuàng)建一個(gè)多選 Checkbox 樹組件,該組件可以用于展示樹形結(jié)構(gòu)的數(shù)據(jù),并允許用戶選擇多個(gè)節(jié)點(diǎn),感興趣的可以了解下

在本篇博客中,我們將使用 React 和 MUI(Material-UI)庫來創(chuàng)建一個(gè)多選 Checkbox 樹組件。該組件可以用于展示樹形結(jié)構(gòu)的數(shù)據(jù),并允許用戶選擇多個(gè)節(jié)點(diǎn)。

前提

在開始之前,確保你已經(jīng)安裝了以下依賴:

  • React
  • MUI(Material-UI)

最終樣式

非全選狀態(tài)

全選狀態(tài)

思路

我們的目標(biāo)是創(chuàng)建一個(gè)多選 Checkbox 樹組件,它可以接收樹節(jié)點(diǎn)數(shù)據(jù),并根據(jù)用戶的選擇返回選中的節(jié)點(diǎn)數(shù)據(jù)。為了實(shí)現(xiàn)這個(gè)目標(biāo),我們將按照以下步驟進(jìn)行:

創(chuàng)建一個(gè) React 函數(shù)組件 CheckBoxTree,它接收一個(gè) data 屬性作為樹節(jié)點(diǎn)數(shù)據(jù),并可選地接收一個(gè) handleCheckData 屬性作為回調(diào)函數(shù),用于傳遞選中的節(jié)點(diǎn)數(shù)據(jù)。

在組件的狀態(tài)中,創(chuàng)建一個(gè) selected 數(shù)組,用于存儲(chǔ)選中的節(jié)點(diǎn)的 id。

實(shí)現(xiàn)一個(gè) onCheck 函數(shù),用于處理節(jié)點(diǎn) Checkbox 的點(diǎn)擊事件。在該函數(shù)中,我們將根據(jù)用戶的選擇更新 selected 數(shù)組,并遞歸處理子節(jié)點(diǎn)的選中狀態(tài)。

實(shí)現(xiàn)一個(gè) renderTree 函數(shù),用于遞歸渲染樹節(jié)點(diǎn)。在該函數(shù)中,我們將根據(jù)節(jié)點(diǎn)的選中狀態(tài)和子節(jié)點(diǎn)的數(shù)量來渲染 Checkbox 和節(jié)點(diǎn)名稱。

使用 TreeView 和 TreeItem 組件來展示樹形結(jié)構(gòu),并將樹節(jié)點(diǎn)數(shù)據(jù)傳遞給 renderTree 函數(shù)進(jìn)行渲染。

步驟

下面是實(shí)現(xiàn)多選 Checkbox 樹組件的詳細(xì)步驟:

1. 創(chuàng)建 React 函數(shù)組件

首先,我們需要?jiǎng)?chuàng)建一個(gè) React 函數(shù)組件 CheckBoxTree,并定義它的屬性和狀態(tài)。代碼如下:

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. 分割父節(jié)點(diǎn)

接下來,我們定義了splitNodeId函數(shù),用于將節(jié)點(diǎn)id拆分為所有父節(jié)點(diǎn)id。它接受一個(gè)節(jié)點(diǎn)id字符串,格式為'1_2_3',并返回一個(gè)父節(jié)點(diǎn)id數(shù)組,例如['1_2', '1']。3表示的是當(dāng)前節(jié)點(diǎn)。

/**
 * 拆分節(jié)點(diǎn)id為所有父節(jié)點(diǎn)id
 * @param id 節(jié)點(diǎn)id,格式為'1_2_3'
 * @returns 父節(jié)點(diǎn)id數(shù)組,如['1_2', '1']
 */
function splitNodeId(id: string) {
  // 按'_'分割節(jié)點(diǎn)id
  const path = id.split('_');

  // 累加生成父節(jié)點(diǎn)id
  return path.reduce((result: string[], current) => {
    // 拼接'_'和當(dāng)前節(jié)點(diǎn)
    result.push(`${result.at(-1) ? result.at(-1) + '_' : ''}${current}`);
    return result;
  }, []);
}

3. 實(shí)現(xiàn)節(jié)點(diǎn) Checkbox 的點(diǎn)擊事件處理函數(shù)

接下來,我們需要實(shí)現(xiàn)一個(gè) onCheck 函數(shù),用于處理節(jié)點(diǎn) Checkbox 的點(diǎn)擊事件。在該函數(shù)中,我們將根據(jù)用戶的選擇更新 selected 數(shù)組,并遞歸處理子節(jié)點(diǎn)的選中狀態(tài)。代碼如下:

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. 實(shí)現(xiàn)遞歸渲染樹節(jié)點(diǎn)的函數(shù)

然后,我們需要實(shí)現(xiàn)一個(gè) renderTree 函數(shù),用于遞歸渲染樹節(jié)點(diǎn)。在該函數(shù)中,我們將根據(jù)節(jié)點(diǎn)的選中狀態(tài)和子節(jié)點(diǎn)的數(shù)量來渲染 Checkbox 和節(jié)點(diǎn)名稱。代碼如下:

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 (
    <TreeItem
      key={nodes.id.id}
      nodeId={nodes.id.id}
      label={
        <FormControlLabel
          onClick={(e) => e.stopPropagation()}
          control={
            <Checkbox
              name={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. 渲染樹形結(jié)構(gòu)

最后,我們使用 TreeView 和 TreeItem 組件來展示樹形結(jié)構(gòu),并將樹節(jié)點(diǎn)數(shù)據(jù)傳遞給 renderTree 函數(shù)進(jìn)行渲染。代碼如下:

return (
  <TreeView
    aria-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;
}

// 組件狀態(tài)
int
erface CheckboxTreeState {
  // 選中節(jié)點(diǎn)id數(shù)組
  selected: string[];
}

// 組件屬性
interface CheckBoxTreeProps {
  // 樹節(jié)點(diǎn)數(shù)據(jù)
  data: RegionType[];
  // 向外傳遞選擇框數(shù)據(jù),
  handleCheckData?: (data: string[]) => void;
}

/**
 * 拆分節(jié)點(diǎn)id為所有父節(jié)點(diǎn)id
 * @param id 節(jié)點(diǎn)id,格式為'1_2_3'
 * @returns 父節(jié)點(diǎn)id數(shù)組,如['1_2', '1']
 */
function splitNodeId(id: string) {
  // 按'_'分割節(jié)點(diǎn)id
  const path = id.split('_');

  // 累加生成父節(jié)點(diǎn)id
  return path.reduce((result: string[], current) => {
    // 拼接'_'和當(dāng)前節(jié)點(diǎn)
    result.push(`${result.at(-1) ? result.at(-1) + '_' : ''}${current}`);
    return result;
  }, []);
}

/**
 * 多選Checkbox樹組件
 * @param props 組件屬性
 * @returns JSX組件
 */
export default function CheckBoxTree(props: CheckBoxTreeProps) {
  // 獲取樹節(jié)點(diǎn)數(shù)據(jù)
  const { data, handleCheckData } = props;

  // 組件狀態(tài):選中節(jié)點(diǎn)id數(shù)組
  const [state, setState] = React.useState<CheckboxTreeState>({
    selected: []
  });

  /**
   * 點(diǎn)擊節(jié)點(diǎn)Checkbox觸發(fā)
   * @param event 事件對象
   * @param node 節(jié)點(diǎn)對象
   * @param parentNodeName 父節(jié)點(diǎn)名稱
   */
  const onCheck = (
    event: React.ChangeEvent<HTMLInputElement>,
    node: RegionType,
    parentNodeName?: string
  ) => {
    // 獲取Checkbox選中狀態(tài)
    const { checked } = event.target;

    // 當(dāng)前節(jié)點(diǎn)id
    const currentId = parentNodeName ?
      `${parentNodeName}_${node.id.id}` :
      node.id.id;

    // 父節(jié)點(diǎn)id數(shù)組
    const parentAreaName = splitNodeId(currentId);

    // 選中狀態(tài):選中當(dāng)前節(jié)點(diǎn)和父節(jié)點(diǎn)
    if (checked) {
      setState((prevState) => ({
        ...prevState,
        //使用Set對selected數(shù)組去重
        selected: Array.from(
          new Set([...prevState.selected, ...parentAreaName])
        )
      }));

      // 若有子節(jié)點(diǎn),遞歸選中
      if (node.children && node.children.length > 0) {
        node.children.forEach((item) => {
          onCheck(event, item, currentId);
        });
      }
    } else if (!checked) {
      // 臨時(shí)state
      let tempState = { ...state };

      // 逆序遍歷,進(jìn)行選中狀態(tài)更新
      for (let index = parentAreaName.length - 1; index >= 0; index--) {
        const element = parentAreaName[index];

        // 若父區(qū)域已無選中節(jié)點(diǎn),取消選中父區(qū)域
        if (
          tempState.selected.filter((id) => id.startsWith(`${element}_`))
            .length === 0
        ) {
          tempState = {
            ...tempState,
            selected: tempState.selected.filter((id) => id !== element)
          };
        }

        // 取消選中當(dāng)前區(qū)域
        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);
    }
  };

  /**
   * 遞歸渲染樹節(jié)點(diǎn)
   * @param nodes 樹節(jié)點(diǎn)數(shù)組
   * @param parentNodeName 父節(jié)點(diǎn)名稱
   * @returns JSX組件
   */
  const renderTree = (nodes: RegionType, parentNodeName?: string) => {
    // 子節(jié)點(diǎn)總數(shù)
    let currentLength = 0;

    /**
     * 獲取子節(jié)點(diǎn)總數(shù)
     * @param currentNodes 當(dāng)前節(jié)點(diǎn)
     */
    function getNodeLength(currentNodes: RegionType) {
      currentNodes.children?.forEach((node) => {
        currentLength++;
        if (node.children) {
          getNodeLength(node);
        }
      });
    }

    // 當(dāng)前節(jié)點(diǎn)id
    const currentId = parentNodeName ?
      `${parentNodeName}_${nodes.id.id}` :
      nodes.id.id;

    // 獲取當(dāng)前節(jié)點(diǎn)子節(jié)點(diǎn)總數(shù)
    getNodeLength(nodes);

    return (
      <TreeItem
        key={nodes.id.id}
        nodeId={nodes.id.id}
        sx={{
          '.MuiTreeItem-label': {
            'maxWidth': '100%',
            'overflow': 'hidden',
            'wordBreak': 'break-all',
            '.MuiFormControlLabel-label': {
              pt: '2px'
            }
          }
        }}
        label={
          <FormControlLabel
            onClick={(e) => e.stopPropagation()}
            sx={{ alignItems: 'flex-start', mt: 1 }}
            control={
              <Checkbox
                name={nodes.name}
                sx={{ pt: 0 }}
                checked={
                  // 若有子節(jié)點(diǎn),判斷子節(jié)點(diǎn)是否全部選中
                  // 或節(jié)點(diǎn)自身是否選中
                  nodes.children &&
                    nodes.children.length &&
                    state.selected.filter((id) =>
                      id.startsWith(`${currentId}_`)
                    ).length === currentLength ||
                  state.selected.some((id) => id === currentId)
                }
                indeterminate={
                  // 子節(jié)點(diǎn)存在選中與非選中狀態(tài)
                  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>
    );
  };

  /**
   * 組件加載時(shí)觸發(fā),獲取去重后的多選框id列表
   */
  React.useEffect(() => {
    // state.selected拆分?jǐn)?shù)組并合并,返回成一個(gè)數(shù)組,如果需要去重后的值,可以使用Array.from(new set)
    const checkBoxList = state.selected.flatMap((item) => item.split('_'));
    // 因?yàn)槭峭ㄟ^parent id來綁定子元素,所以下面的元素是只返回最后的子元素
    const checkTransferList = checkBoxList.filter(
      (value) => checkBoxList.indexOf(value) === checkBoxList.lastIndexOf(value)
    );

    // 從多選值數(shù)組中生成集合Set,再使用Array.from轉(zhuǎn)換為數(shù)組
    if (handleCheckData) {
      handleCheckData(checkTransferList);
    }
  }, [state]);

  React.useEffect(() => {
    if (data.length) {
      setState({ selected: [] });
    }
  }, [data]);

  return (
    <TreeView
      aria-label="checkbox tree"
      defaultCollapseIcon={<ExpandMore />}
      defaultExpandIcon={<ChevronRight />}
      disableSelection={true}
    >
      {data.map((item) => {
        return renderTree(item);
      })}
    </TreeView>
  );
}

總結(jié)

通過以上步驟,我們成功地創(chuàng)建了一個(gè)多選 Checkbox 樹組件。該組件可以接收樹節(jié)點(diǎn)數(shù)據(jù),并根據(jù)用戶的選擇返回選中的節(jié)點(diǎn)數(shù)據(jù)。我們使用了 React 和 MUI(Material-UI)庫來實(shí)現(xiàn)這個(gè)功能,并按照前提、思路和步驟的順序進(jìn)行了解析和實(shí)現(xiàn)。

以上就是詳解如何使用React和MUI創(chuàng)建多選Checkbox樹組件的詳細(xì)內(nèi)容,更多關(guān)于React MUI創(chuàng)建多選Checkbox樹組件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React Native 集成jpush-react-native的示例代碼

    React Native 集成jpush-react-native的示例代碼

    這篇文章主要介紹了React Native 集成jpush-react-native的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • React高級(jí)概念之Context用法詳解

    React高級(jí)概念之Context用法詳解

    在React應(yīng)用中,為了讓數(shù)據(jù)在組件間共享,常見的方式是讓它們以props的形式自頂向下傳遞,如果數(shù)據(jù)要在組件樹不同層級(jí)共享,那么這些數(shù)據(jù)必須逐層傳遞直到目的地,Context如同管道,它將數(shù)據(jù)從入口直接傳遞到出口,使用Context能避免“prop-drilling”
    2023-06-06
  • Reactjs?+?Nodejs?+?Mongodb?實(shí)現(xiàn)文件上傳功能實(shí)例詳解

    Reactjs?+?Nodejs?+?Mongodb?實(shí)現(xiàn)文件上傳功能實(shí)例詳解

    今天是使用?Reactjs?+?Nodejs?+?Mongodb?實(shí)現(xiàn)文件上傳功能,前端我們使用?Reactjs?+?Axios?來搭建前端上傳文件應(yīng)用,后端我們使用?Node.js?+?Express?+?Multer?+?Mongodb?來搭建后端上傳文件處理應(yīng)用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-06-06
  • React狀態(tài)管理的項(xiàng)目實(shí)踐

    React狀態(tài)管理的項(xiàng)目實(shí)踐

    本文主要介紹了狀態(tài)管理的概念,包括本地狀態(tài)和全局狀態(tài)的管理方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-01-01
  • ReactDOM.render在react源碼中執(zhí)行原理

    ReactDOM.render在react源碼中執(zhí)行原理

    這篇文章主要為大家介紹了ReactDOM.render在react源碼中執(zhí)行原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 淺析history 和 react-router 的實(shí)現(xiàn)原理

    淺析history 和 react-router 的實(shí)現(xiàn)原理

    react-router 版本更新非???但是它的底層實(shí)現(xiàn)原理確是萬變不離其中,在本文中會(huì)從前端路由出發(fā)到 react-router 原理總結(jié)與分享,本文對history 和 react-router實(shí)現(xiàn)原理講解的非常詳細(xì),需要的朋友跟隨小編一起看看吧
    2023-08-08
  • React 組件中的 bind(this)示例代碼

    React 組件中的 bind(this)示例代碼

    這篇文章主要介紹了 React 組件中的 bind(this) ,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-09-09
  • redux功能強(qiáng)大的Middleware中間件使用學(xué)習(xí)

    redux功能強(qiáng)大的Middleware中間件使用學(xué)習(xí)

    這篇文章主要為大家介紹了redux功能強(qiáng)大的Middleware中間件使用學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • React Router中Link和NavLink的學(xué)習(xí)心得總結(jié)

    React Router中Link和NavLink的學(xué)習(xí)心得總結(jié)

    這篇文章主要介紹了React Router中Link和NavLink的學(xué)習(xí)心得總結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 如何使用Next.js?+?Prisma?+?MySQL開發(fā)全棧項(xiàng)目

    如何使用Next.js?+?Prisma?+?MySQL開發(fā)全棧項(xiàng)目

    在Next.js項(xiàng)目的任何地方都可以使用Prisma來訪問數(shù)據(jù)庫,Prisma提供了豐富的查詢方法,滿足您在項(xiàng)目中的各種需求,這篇文章主要介紹了如何使用Next.js+Prisma+MySQL開發(fā)全棧項(xiàng)目的相關(guān)資料,需要的朋友可以參考下
    2026-03-03

最新評論

海南省| 舒城县| 东方市| 忻州市| 饶阳县| 东平县| 姚安县| 筠连县| 南江县| 安吉县| 富民县| 九江县| 兴仁县| 丽水市| 祁阳县| 崇州市| 琼中| 湟中县| 利津县| 曲阜市| 福安市| 开封县| 白玉县| 内江市| 桑植县| 永靖县| 荆门市| 翁源县| 西充县| 济源市| 文昌市| 永泰县| 漠河县| 贵南县| 宣汉县| 青州市| 克东县| 岳普湖县| 霍城县| 侯马市| 沐川县|