IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React-View-UI组件库封装——Tree选择器 -> 正文阅读

[JavaScript知识库]React-View-UI组件库封装——Tree选择器

Tree选择器结构比较特殊,类似于数据结构中的树,因此设计对于优化有很多的关系。

先看一下组件库文档
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
设计中主要用到的思路是递归,先看一下基础渲染吧:

const treeData = [
  {
    title: 'parent1',
    value: '0-0',
    children: [
      {
        title: 'parent 1-0',
        value: '0-0-1',
      },
      {
        title: 'parent 1-1',
        value: '0-0-2',
        children: [
          {
            title: 'leaf2',
            value: '0-0-0-1',
          },
        ],
      },
    ],
  },
  {
    title: 'parent2',
    value: '0-1',
    children: [
      {
        title: 'parent 2-0',
        value: '0-0-3',
      },
    ],
  },
];

渲染结构是这样的,就是一个树的结构,通过渲染函数将所有树节点递归渲染出来,核心代码如下:
在这里插入图片描述
在这里插入图片描述
而具体这个level是从哪里来的呢,其实使用者上文只需要传递title、value、children即可,设计中其实是在渲染之前对这个树节点进行了一些结构改造的,以便于组件开发。
在这里插入图片描述
在上图中,二次改造函数对每个节点都进行了height和level的计算和添加,这些后面都会用到,具体备注在图片中很清楚。

业务开发
核心点主要在切换菜单,切换菜单时我的设计是展开只进行下一层节点的展示;收起的话如果是对根节点进行收起操作,则将所有子节点收起,核心代码如下:
在这里插入图片描述
上面所讲的是切换的实现,如果是点击无子节点的节点呢?就是直接选中操作了。
在这里插入图片描述
这里选中分为了单选了多选,组件默认是单选的,如需要支持多选,需要给组件传递avaChooseMore属性,具体可参照文档案例。

组件完整源码index.tsx:

import React, { FC, memo, Fragment, useState, useEffect, useCallback } from 'react';
import { CaretRightOutlined, CaretDownOutlined } from '@ant-design/icons';
import Input from '../Input';
import './index.module.less';

interface treeProps {
  /**
   * @description Tree配置参数
   */
  treeData: Array<treeNode>;
  /**
   * @description 宽度
   * @default 200px
   */
  width?: string;
  /**
   * @description 支持搜索
   * @default false
   */
  avaSearch?: boolean;
  /**
   * @description 支持多选
   * @default false
   */
  avaChooseMore?: boolean;
  /**
   * @description 全展开
   * @default false
   */
  defaultOpen?: boolean;
  /**
   * @description 选择回调函数
   */
  chooseCallback?: Function;
}
interface treeNode {
  title: string;
  value: string;
  level: number;
  height?: string;
  children?: Array<treeNode>;
}

const Tree: FC<treeProps> = (props) => {
  const { width = '200', treeData, avaSearch, avaChooseMore, defaultOpen, chooseCallback } = props;

  const [stateTreeData, setStateTreeData] = useState<Array<treeNode>>(treeData); //树结构
  const [activedVal, setActivedVal] = useState<string>(''); //选中的节点值
  const [containerHeight, setContainerHeight] = useState<string>('0px'); //容器高度
  const [isFocus, setIsFocus] = useState(false); //聚焦状态

  useEffect(() => {
    resolveTreeData(treeData as Array<treeNode>, 1);
    window.addEventListener('click', () => setContainerHeight('0px'));
  }, []);

  const resolveTreeData = (treeData: Array<treeNode>, nowIndexLevel: number) => {
    //二次处理treeData
    treeData.forEach((treeNode: treeNode) => {
      treeNode.level = nowIndexLevel;
      if (defaultOpen) {
        //默认全展开
        treeNode.height = '30px';
      } else {
        treeNode.height = treeNode.level == 1 ? '30px' : '0';
      }
      if (treeNode?.children?.length) {
        //有子节点
        resolveTreeData(treeNode.children, nowIndexLevel + 1);
      } else {
        //没有子节点,重置level为当前层级,继续寻找
        nowIndexLevel = treeNode.level;
      }
    });
    setStateTreeData(treeData); //更新状态
  };
  const toggleTreeMenu = (clickTreeNode: treeNode) => {
    //菜单切换或直接选中终极节点
    if (clickTreeNode?.children?.length) {
      //菜单切换的情况
      const oldStateTree = [...stateTreeData];
      const editTreeNode = (treeNode: Array<treeNode>) => {
        //所选节点后代收起处理函数
        treeNode.forEach((child) => {
          //找到节点,对子节点进行处理
          if (child?.children?.length) {
            child.height = '0';
            editTreeNode(child.children);
          } else {
            child.height = '0';
          }
        });
      };
      const mapFn = (treeNode: Array<treeNode>) => {
        //深度优先查找节点函数
        treeNode.forEach((t: treeNode, i: number) => {
          if (t.title == clickTreeNode.title && t.value == t.value) {
            if (t?.children?.length) {
              //后代节点处理,如果打开,只需打开下一代即可,如果关闭,需要关闭所有后代
              if (t.children[0].height == '0') {
                //打开
                t.children = t.children.map((child: treeNode) => {
                  return {
                    ...child,
                    height: child.height == '0' ? '30px' : '0',
                  };
                });
              } else {
                //关闭
                editTreeNode(t.children); //对后代节点进行处理
              }
            }
          } else if (t?.children?.length) {
            mapFn(t.children);
          }
        });
      };
      mapFn(oldStateTree);
      setStateTreeData(oldStateTree);
    } else {
      //选中终极节点的情况
      if (avaChooseMore) {
        //多选
        if (activedVal.split(',').includes(clickTreeNode.title)) {
          //取消选中
          let updateVal: Array<string> | string = activedVal;
          updateVal = updateVal.split(',');
          updateVal.splice(
            activedVal.split(',').findIndex((t) => t == clickTreeNode.title),
            1,
          );
          updateVal = updateVal.join(',');
          setActivedVal(updateVal);
          chooseCallback && chooseCallback(updateVal);
        } else {
          setActivedVal(
            activedVal == '' ? clickTreeNode.title : activedVal + ',' + clickTreeNode.title,
          );
          chooseCallback &&
            chooseCallback(
              activedVal == '' ? clickTreeNode.title : activedVal + ',' + clickTreeNode.title,
            );
        }
      } else {
        //单选
        setActivedVal(clickTreeNode.title);
        chooseCallback && chooseCallback(clickTreeNode.title);
      }
    }
  };
  const handleIptChange = (val: string) => {
    //文本改变回调
    if (avaSearch) {
      setActivedVal(val);
    } else {
      setActivedVal('');
    }
  };
  const handleClick = () => {
    //点击回调
    if (avaSearch) {
      if (isFocus && containerHeight == '100%') {
        setContainerHeight('0px');
      } else {
        setContainerHeight('100%');
      }
    } else {
      setContainerHeight(containerHeight == '0px' ? '100%' : '0px');
    }
  };
  const handleIptFocus = () => {
    //聚焦回调
    setTimeout(() => {
      //异步,等待点击执行完毕
      if (!isFocus) {
        setIsFocus(true);
      }
    }, 150);
  };
  const handleIptBlur = () => {
    //失去焦点回调
    setIsFocus(false);
  };
  const searchStyle = useCallback(
    (treeNode: treeNode): string => {
      //搜索高亮样式
      if (treeNode.title.includes(activedVal) && activedVal !== '') {
        return '#1890FF';
      } else {
        return '#000000';
      }
    },
    [activedVal],
  );
  const activedStyle = useCallback(
    (treeNode: treeNode): string => {
      //选中高亮样式
      if (avaChooseMore) {
        //多选
        if (activedVal.split(',').includes(treeNode.title)) {
          return '#bae8ff';
        } else {
          return '#ffffff';
        }
      } else {
        //单选
        if (activedVal == treeNode.title) {
          return '#bae8ff';
        } else {
          return '#ffffff';
        }
      }
    },
    [activedVal],
  );
  const clearCallback = () => {
    //清空
    setActivedVal('');
  };
  const render = (data: Array<treeNode> = stateTreeData) => {
    //动态规划render函数
    return data.map((treeNode: treeNode, index) => {
      return (
        <Fragment key={index}>
          <div
            className="treeNode"
            style={{
              marginLeft: `${treeNode.level * 10}px`,
              height: `${treeNode.height}`,
              color: searchStyle(treeNode),
              background: activedStyle(treeNode),
            }}
            onClick={() => toggleTreeMenu(treeNode)}
          >
            {
              treeNode?.children?.length ? (
                treeNode.children[0].height == '0' ? (
                  <CaretDownOutlined />
                ) : (
                  <CaretRightOutlined />
                )
              ) : (
                <div style={{ width: '12px', height: '12px' }}></div>
              ) //空间占位符
            }

            <span className="text">{treeNode.title}</span>
          </div>
          {treeNode?.children?.length && render(treeNode.children)}
        </Fragment>
      );
    });
  };

  return (
    <Fragment>
      <div className="tree-container" onClick={(e) => e.stopPropagation()}>
        <Input
          moreStyle={avaSearch ? {} : { caretColor: 'transparent' }}
          placeholder={avaSearch ? '请输入' : ''}
          width={width}
          defaultValue={activedVal}
          handleClick={handleClick}
          handleIptChange={handleIptChange}
          handleIptFocus={handleIptFocus}
          handleIptBlur={handleIptBlur}
          clearCallback={clearCallback}
          showClear
        />

        <div
          className="tree-select-dialog"
          style={{
            width: `${width}px`,
            height: containerHeight,
            opacity: containerHeight == '0px' ? '0' : '1',
            padding: containerHeight == '0px' ? '0 10px 0 10px' : '10px',
          }}
        >
          {render()}
        </div>
      </div>
    </Fragment>
  );
};

export default memo(Tree);

github path戳~
React-View-UI组件库文档地址戳~

如果对该组件库有兴趣,在github的readme中有介绍基本入门方式,可以下载尝试,最后感谢花时间阅读此文,如果对Tree组件设计有更好的建议欢迎留言呀。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-06-03 23:55:30  更:2022-06-03 23:56:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:12:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码