React 组件封装之 Tree 树形控件

React 组件封装之 Tree 树形控件

前端处理的数据越来越复杂,嵌套的树形结构数据也越来越多,所以自己封装一个树形控件非常有必要,可以练习自己对树形控件的理解和数据的操作联系。

Tree 树形结构数据

这就是一个简单的树形结构数据

 const Data = [
    {
      title: '一级菜单',
      key: '0',
      children: [
        {
          title: '二级菜单0',
          key: '0-0',
          disabled: true,
          children: [
            {
              title: '三级菜单0',
              key: '0-0-0',
              disableCheckbox: true,
            },
            {
              title: '三级菜单1',
              key: '0-0-1',
            },
          ],
        },
        {
          title: '二级菜单1',
          key: '0-1',
          children: [{ title: <span style={{ color: '#1677ff' }}>菜单</span>, key: '0-0-1-0' }],
        },
      ],
    },
  ];

封装流程

  • 源代码

下面是一个源代码 里面有注释,标注我封装组件时的一个流程,代码难懂的理解,本人技术一般,见谅。

import React, { FC, useState, useRef, memo } from 'react';
import { TreeProps } from './interface';
import Css from './index.module.less';
import Icon from '../Icon';
const Tree: FC<TreeProps> = memo(({ treeData = [], handleChange, a, left }) => {
  let [keys, setKeys] = useState([]);
  const sonRef = useRef(null);
  // 点击事件
  const handle = (e: any, item: any) => {
    // 判断是否传入回调函数
    handleChange ? handleChange(item) : null;
    // 判断是否有子组件 是否需要展开, 不需要展开直接结束函数
    if (!item.children || (e.target.localName !== 'span' && e.target.localName !== 'svg')) {
      return;
    }
    let newKeys: any = keys;
    let index = newKeys.findIndex((v: any) => v === item.key);
    // 获取需要展开的dom元素
    let ref = e.target.parentNode.nextSibling;
    // 判断点击是否含有key值 如果已有key值,说明元素已展开
    if (index >= 0) {
      newKeys.splice(index, 1);
      // 箭头旋转会原始角度
      e.target.parentNode.children[0].style.transform = 'rotate(0deg)';
      // 收缩元素
      ref.style.height = 0 + 'px';
      // 收缩删除类名
      ref.classList.remove(Css['tree_show']);
      // 如果没有key值,说明元素未展开
    } else {
      newKeys.push(item.key);
      // 箭头旋转90度
      e.target.parentNode.children[0].style.transform = 'rotate(90deg)';
      // 展开元素
      ref.style.height = ref.scrollHeight + 'px';
      // 点击的非一级菜单高度自适应
      if (a) {
        a.current.style.height = 'auto';
      }
      // 展开添加类名
      ref.classList.add(Css['tree_show']);
    }
    // 把修改后keys值更新
    setKeys(newKeys);
  };
  return (
    <div className={Css['tree_father']} style={{ marginLeft: left ? 1 * left + 'px' : '0px' }}>
      {treeData.map((item: any) => {
        return (
          <div key={item.key}>
            {/* 标题 */}
            <div className={Css['title']} onClick={(e) => handle(e, item)}>
              {item.children ? (
                <Icon name={'arrow-rightjiantou-you2'} className={Css['icon']} size={14} />
              ) : null}
              <span className={Css['title_text']}>{item.title}</span>
            </div>

            {/* 子菜单 */}
            {item.children ? (
              <div className={Css['tree_son']} ref={sonRef}>
                <Tree
                  treeData={item.children}
                  left={left ? left : 16}
                  // 上一级元素
                  a={sonRef}
                  // 自定义事件
                  handleChange={handleChange ? handleChange : () => { }}
                />
              </div>
            ) : null}
          </div>
        );
      })}
    </div>
  );
});

export default Tree;
  • 样式

使用到的样式文件

.tree_father {
  display: inline-block;
  user-select: none;

  .title {
    display: flex;
    align-items: center;
    padding: 2px;
  }

  .icon {
    transition: all 0.3s;
  }

  .title_text {
    display: block;
    margin-left: 5px;
    cursor: pointer;

    &:hover {
      background-color: rgba(0, 0, 0, 0.04);
    }
  }

  .tree_show {
    height: auto;
    transition: all 0.3s;
  }
}

.text {
  .title {
    padding: 5px;
  }
}

.tree_son {
  height: 0px;
  overflow: hidden;
  transition: all 0.3s;
}

  • 类型声明

使用到的类型声明文件

export interface TreeProps {
  /**
   * @description 列表数据
   * @default []
   */
  treeData?: any;
  /**
   * @description 点击触发事件
   */
  handleChange?: Function;
  // /**
  //  * @description 数控组件类型,不同类型不同样式 可选 button text border
  //  * @default text
  //  */
  // type?: string
  left?: any;
  a?: any
}

总结

实现树形控件主要就是一个函数组件的一个递归实现,先规定树形数据结构,通过数据结构实现组件递归,判断递归的结束条件,当没有children字段时结束递归,剩下就是一个样式的写法,怎么控制样式显示隐藏实现交互效果,我这个实现效果不是太好,局限性较差,过渡效果不是太好,我是通过超出隐藏和控制高度实现过渡效果。实现并不难,实现方法有很多,我这个写的不是太好需要优化的地方有很多。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要使用 ElementUI 的 Tree 树形控件做侧边导航栏,可以按照以下步骤进行: 1. 安装 ElementUI 组件库: ```bash npm install element-ui -S ``` 2. 引入 Tree 组件: ```javascript import { Tree } from 'element-ui'; ``` 3. 在模板中使用 Tree 组件: ```html <template> <div> <el-tree :data="treeData" :props="treeProps"></el-tree> </div> </template> ``` 4. 在脚本中定义 treeData 和 treeProps 变量: ```javascript export default { data() { return { treeData: [ { label: '首页', icon: 'el-icon-s-home', path: '/' }, { label: '文章', icon: 'el-icon-document', children: [ { label: 'JavaScript', path: '/article/javascript' }, { label: 'Vue', path: '/article/vue' }, { label: 'React', path: '/article/react' } ] }, { label: '关于', icon: 'el-icon-info', path: '/about' } ], treeProps: { label: 'label', children: 'children', isLeaf: node => !node.children, icon: node => node.icon } }; } }; ``` 其中,treeData 是树形数据,treeProps 是 Tree 组件的属性配置。在这里,我们使用 label 属性作为节点显示文本,children 属性表示子节点,isLeaf 函数用于判断是否为叶子节点,icon 函数用于设置节点图标。 5. 根据 path 属性实现路由跳转: ```javascript export default { methods: { handleNodeClick(node) { if (node.path) { this.$router.push(node.path); } } } }; ``` 在 Tree 组件上绑定 node-click 事件,当节点被点击时,判断是否有 path 属性,如果有则使用 Vue Router 进行路由跳转。 这样,我们就可以使用 ElementUI 的 Tree 组件快速实现一个侧边导航栏了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值