想实践一个树形组件的起因是发现目前主流UI库在树形组件上都没有提供连接线(ant design有,但不知道为什么设计得发虚,就是各元素之间没有严格衔接上,见下图,而Vue生态圈中的Element UIiView压根都不支持)。

对我这种没有钛合金狗眼的人来说常常会看歪了。

所以自己想禽兽,啊不,亲手尝试一个。仅仅实践最最基本的树形结构的展现、展开、折叠,不包含高级的多选、拖动、自定义渲染等功能。

弄完确实感受到只有亲自动手做的时候才悟到了很多的为什么。

起步

Vue CLI直接生成一个Vue工程,删除不必要的文件,把它跑起来。

从一个第三方的树形组件库里扒拉一张图片过来

另外连接线也有一张图片,但实际内容只有一个点,通过不停的重复给人一种连接线的视觉效果,我就不放出来了。

看了下其他UI库的做法,Element UIiView都用的是图标也即是IconFont,而antd居然用的是SVG,真是不走寻常路。

数据和组件的准备

先准备好有代表性的数据,其中expand字段表示默认该节点是否展开。

 [{"label": "Node1","expand": true,"key": "1","children": [{"label": "Node1-1","key": "1-1"},{"label": "Node1-2","key": "1-2"},{"label": "Node1-3","key": "1-3","children": [{"label": "Node1-3-1","key": "1-3-1"},{"label": "Node1-3-2","key": "1-3-2"}]},{"label": "Node1-4","key": "1-4"}]},{"label": "Node2","key": "2"},{"label": "Node3","expand": false,"key": "3","children": [{"label": "Node3-1","key": "3-1"},{"label": "Node3-2","key": "3-2"},{"label": "Node3-3","key": "3-3"}]}]

再准备一下组件文件,我是这样抽象的,先把树形组件里每一个节点拿出来作为一个组件,它们共性的地方都是一个物理上的节点,都要展示节点的名称,不论这个节点是不是叶子节点;然后一套的父 + 子节点拿出来也作为一个组件,这个时候我心里其实已经计划好使用递归了。

于是创建了AwesomeTree.vueAwesomeTreeNode.vue两个组件文件。

组件Props

组件Props的事情肯定是迭代做的,不可能一次性把所有的场景都想全。

比如一开始,我想到的就仅是数据作为Props传入,在<awesome-tree>上增加nodes的属性,在<awesome-tree-node>上增加node的属性,随着开发的深入,在<awesome-tree>上又增加了level表示当前的层级,最后又增加了isParentLastNode表示它的父级节点是不是叔伯节点的最后一个元素。

而在<awesome-tree-node>上,后续也增加了许多像isFirstisLast等辅助性Props。

最终在<awesome-tree>组件上的Props是

 export default {props: {nodes: { // 数据type: Array,default: function () {return [];},},level: { // 层级type: Number,default: 0,},isParentLastNode: { // 父节点是否是最后一个,影响到连接线type: Boolean,default: false,},},}

最终在<awesome-tree-node>组件上的Props是

 export default {props: {node: { // 节点数据type: Object,default: function () {return {};},},level: { // 层级type: Number,default: 0,},isFirst: { // 是否第一个节点type: Boolean,default: false,},isLast: { // 是否最后一个节点type: Boolean,default: false,},isLeaf: { // 是否是叶子节点type: Boolean,default: true,},isExpand: { // 是否展开type: Boolean,default: false,},toggle: { // 折叠的方法,因为用上了JSXtype: Function,},},}

组件渲染

先说一下最终的DOM,经过观察,发现antdElement UI两个组件渲染树形节点之后,每个节点都是一个<div>,这样子,似乎缺少了语义化,毕竟从实际角度上观察,树的节点也是一个个的列表项。

iView的节点渲染后,是<ul><li>,但瑕疵是存在DOM的冗余,看下图

我一开始做的方式也是相同的,白白在一个<li>上多套了一层冗余的<ul>,后来在修改的过程中发现单文件组件里的<template>并不能满足树形组件对DOM的灵活要求,但是我又不甘心写render()函数,毕竟用createElement()把虚拟DOM堆起来也是要很花力气的,所以最终折中了一下——使用JSX,好在Vue CLI中已经支持了JSX

一开始我以为Vue里的JSXReact里使用JSX是完全一样的,但现实又一次无情地压垮了我。

我把我碰到的问题总结在下面,建议在Vue里是用JSX之前,还是先看一下文档。

  1. 不需要使用className替代class,但没试过htmlFor
  2. 事件使用vOn:eventName,既不是Vue里的@语法,也不是像React一样传递一个方法进组件
  3. 最终还是只能返回一个节点,没有React里的Fragment或者[]这样子的语法存在
  4. 没有v-ifv-for这样子的语法糖

总而言之,言而总之,如果真的在项目里同时使用<template>JSX甚至render()函数,你的思路一定会非常的乱,编码前先清一下脑子里的水,练习一下左手画圆右手画方。

<awesome-tree>组件里的主旋律就是循环和递归,这里一个坑是递归组件一定要声明name,好在错误提示上已经写明了,也不至于查错查到怀疑人生。

当节点数据存在children时表示这个节点有子节点,可能会引起递归,为什么说是可能呢,因为节点有可能是折叠的,考虑到DOM性能,折叠之后的节点不应该渲染出来,所以就是

 if (node.children && node.children.length > 0) {this.cache[node.key] = node;return (<ul><awesome-tree-nodekey={node.key}node={node}level={this.level + 1}isFirst={index === 0}isLast={index === this.nodes.length - 1}isExpand={node.expand}isLeaf={!node.children || node.children.length === 0}vOn:toggle={this.onToggle}/>{node.expand && (<awesome-tree nodes={node.children} level={this.level + 1} isParentLastNode={index === this.nodes.length - 1} />)}</ul>);}

而如果是没有子节点的叶子结点,直接用<awesome-tree-node>渲染即可

 return (<awesome-tree-nodekey={node.key}node={node}level={this.level + 1}isFirst={index === 0}isLast={index === this.nodes.length - 1}isExpand={node.expand}isLeaf={!node.children || node.children.length === 0}vOn:toggle={this.onToggle}/>);

样式

具体节点的外观都在<awesome-tree-node>组件里实现,这里我发现用图片表示手柄或连接线的缺陷——不能自适应节点大小。一般来说,图片的大小都是固定的,当然即便你准备了多份的图片的资源,也还是不能确保万无一失的。比如我扒拉下来的图片资源是适应18 * 18像素的,也即是节点的高度也应该设置为18像素,但是万一节点元素哪天需要变为36像素,恐怕很难做到自适应,图片资源的放大失真是一个原因,另外还可能引起布局和元素的错位。

由于在<awesome-tree>组件里较好的解决了DOM节点的问题,各层级节点的缩进的实现并不是很困难,一开始我使用了level的Prop,传入后根据这个值计算缩进的距离,在改进了DOM之后,只要在每一个用于包裹子节点的<ul>上设置padding-left即可,深层节点自然具备了缩进的效果,如下图所示

而展开折叠的手柄显示也是一个比较麻烦的点,所以我添加了许多辅助的Props,比如isFirstisLast等,因为位置的不同,所用的手柄图片资源也不相同。我写了一个硕大的判断方法,用来判断到底该用哪个资源

 function computeIndicatorClass() {if (this.isFirst && this.isLast && this.isExpand && !this.isLeaf) {return "unique-expand"; // 唯一的节点,展开状态}if (this.isFirst && this.isLast && !this.isExpand && !this.isLeaf) {return "unique-collapse"; // 唯一的节点,折叠状态}if (this.isFirst && this.isExpand && !this.isLeaf) {return "first-expand"; // 非唯一的第一个节点,展开状态}if (this.isFirst && !this.isExpand && !this.isLeaf) {return "first-collapse"; // 非唯一的第一个节点,折叠状态}if (this.isLast && this.isExpand && !this.isLeaf) {return "last-expand"; // 非唯一的最后一个节点,展开状态}if (this.isLast && !this.isExpand && !this.isLeaf) {return "last-collapse"; // 非唯一的最后一个节点,折叠状态}if (!this.isFirst && !this.isLast && this.isExpand && !this.isLeaf) {return "middle-expand"; // 非唯一的中间节点,展开状态}if (!this.isFirst && !this.isLast && !this.isExpand && !this.isLeaf) {return "middle-collapse"; // 非唯一的中间节点,折叠状态}if (this.isFirst && this.isLast && this.isLeaf) {return "unique-leaf"; // 唯一的叶子节点}if (this.isFirst && this.isLeaf) {return "first-leaf"; // 第一个的叶子节点}if (this.isLast && this.isLeaf) {return "last-leaf"; // 最后一个的叶子节点}if (!this.isFirst && !this.isLast && this.isLeaf) {return "middle-leaf"; // 中间位置的叶子节点}return "";}

有了这些之后,静态的树形展示基本上就实现了。

连接线

再来看一下我的初心——连接线。连接线还真是不好做的,也难怪好多UI库都没有提供连接线的功能,我感觉我是凑出来这个连接线的效果。

连接线的样式很简单,之前也扒拉下来一个点的图片资源,在背景图里设置为repeat-y即有连接线的效果出来。但是连接线存在的场景不是那么简单,比如在下图里

Node3的子节点是不需要再放置连接线的,而Node1下面的子节点都需要。

由此可见,连接线的显示与否,与父节点是否是在最后一个的位置有关,实际开发的时候,没那么快能找到规律,也是不断地尝试后才理清思路。所以我在<awesome-tree>组件上增加了isParentLastNode的Prop,只有在递归的时候需要传入,而它影响到的是包裹的<ul>上的样式

 <ul class={!this.isParentLastNode && this.level ? "line-conn tree" : "tree"}>//...</ul>

.line-conn的样式设置如下

 .line-conn {background-image: url(/line_conn.gif);background-repeat: repeat-y;}

折叠展开操作

最后一步就是点击手柄后进行展开或者折叠,这里就涉及到刚才提到的JSX里怎么传递事件了。在<awesome-tree>里使用vOn:toggle={this.onToggle},而在<awesome-tree-node>里因为是用的<template>,只要正常地emit即可。

 <divclass="indicator"@click="$emit('toggle', node.key)":class="computeIndicatorClass":style="{display: 'inline-block',height: indicatorWidth + 'px',width: indicatorWidth + 'px',}"/>

如此只要在<awesome-tree>里更改数据即可,Vue的响应式会自动更新DOM,这里又稍许有几个注意的地方。

一个是考虑性能,缓存起带有字节点的数据节点,在render()方法里,如果某个节点又有子节点,那么会被缓存起来,下次找寻的时候不需要再遍历了

 if (node.children && node.children.length > 0) {this.cache[node.key] = node;//...}

注意key不能重复,否则会有错乱的问题发生,这也倒过来证明了key的重要性。

另外一个在Vue里也是常见的问题,为了简化,对于没有expand字段的节点默认是折叠的,但是发现这种节点的切换按钮没有效果。当这种情况发生时,最先考虑的就是打印一下数据是否正常,如果数据是符合逻辑的,那么就是Vue没有把该字段纳入它的响应范围,所以onToggle方法应该这么写

 if (this.cache[key].expand === undefined) {this.$set(this.cache[key], "expand", true);} else {this.cache[key].expand = !this.cache[key].expand;}

如果原来没有expand字段,就要使用$set把这个字段收编进来,这样这个字段的变化才会引起DOM的更新。

最后就是动画了,本来以为在折叠状态改变的时候加入动画很简单,jQuery里的slideUp()slideDown()方法当年用起来也是很趁手的,现在改用Vue里的<transition>也是一样的。结果现实却给了我一个大嘴巴子——heightauto的不能动画,无论是用transition还是CSS动画都不行,其实想想也是,这个为autoheight是不明确的,这让引擎也很难办啊。我又翻了翻UI库,iViewElement UI里都用了自己实现的<collapse-transition>,问题是你们的代码怎么完全一模一样的,也没见声明是用了哪个第三方的啊,果真是天下代码一大抄。

后续

后续有时间当然是可以把拖动、编辑、单选、多选、事件等再完善一下,毕竟实践出真知。

最后鸡汤还是要端上来的,最近喜欢的一句话

后来啊,我才明白,太阳都没法做到所有人喜欢,你说它刺眼,我说它温暖,谁能不挨骂呢

el-table 树形表格 自定义展开图标_实践一个树形组件相关推荐

  1. el-table 树形表格 自定义展开图标_耍好控件 | 产品图标体系是如何炼成的?

    温馨提示:建议阅读时间9分钟.文末还为大家准备了网易云音乐Android视觉规范一份,进行学习与参考,记得领取哦~ 前不久我在讲标签栏专题的时候,有聊到过一次图标(可回顾:<了解图标落地,让前端 ...

  2. el-table 树形表格 自定义展开图标_[shell脚本]表格数据在终端可视化输出

    最终效果 1. 自定义表格样式 2. 自定义主题颜色 支持三系普通颜色 支持16色彩虹色 支持单颜色 回顾一下shell语法 1. shell传递参数 我们可以在执行shell脚本时实时传递参数从而指 ...

  3. el-table 树形表格 自定义展开图标_超级好用,如何上传自制图标到iSlide图标库...

    iSlide有很多被忽略的好功能 比如上传功能 用户可上传本地文件到 iSlide图片库.图标库,上传成功的文件可一键插入PPT,图片插入不变形 所有上传的素材仅自己可见 大家常用的是图片上传 却很少 ...

  4. el-table 树形表格 自定义展开图标_图标制作工具 Icon Slate for mac

    Icon Slate for mac(图标制作工具)​www.macdown.com Mac os平台上的一款帮助用户快速制作logo的Mac图标制作软件,Icon Slate mac是一款让您轻松创 ...

  5. el-table 树形表格 自定义展开图标_换图标icon APP下载-换图标icon APP官方版 v3.01.0927...

    换图标icon APP是一款可以带来更多不同风格应用图标和壁纸资源给大家尝试的软件,轻松打造个性化的桌面,资源类型丰富,直接在线去搜索查找就可以了,全部免费下载即可体验,重点是设置方便,壁纸等图片的尺 ...

  6. el-table 树形表格 自定义展开图标_IconJar for Mac(图标素材设计软件)

    IconJar for Mac是一款Mac平台上非常优秀的图标素材设计软件.IconJar for Ma设计风格质朴.功能实用,使用方法简单,拖入式导入即可.IconJar Mac能够浏览设计资源文件 ...

  7. el-table 树形表格 自定义展开图标_Windows图标美化指南,从内到外焕然一新

    之前出过一期win10美化的文章反响还不错,但篇幅原因很多模块没有展开说.这次详细介绍一下Windows各种图标的替换美化教程供大家折腾.文末提供图标资源和涉及到的工具,所有图标都可复原,放心折腾! ...

  8. el-table 树形表格 自定义展开图标_Icon Slate for mac(图标制作工具) v4.5.0

    Synthesia mac版是Mac os平台上的一款帮助用户快速制作logo的Mac图标制作软件,Icon Slate for mac是一款让您轻松创建,输入和输出多种格式电脑和移动终端程序图标的图 ...

  9. el-table 树形表格 自定义展开图标_iOS 14自定义桌面太美了 手把手教你重温青春...

    近日有粉丝朋友留言,想要知道iOS14如何更改APP图标.相信不少朋友,之前已经在网上看过不少关于 iOS14桌面美化的介绍,酷似曾经的装扮QQ空间.由于 iOS14更改APP图标过于简单,今天小编就 ...

最新文章

  1. Linux Shell脚本实现根据进程名杀死进程
  2. springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
  3. Dapper源码学习和源码修改(下篇)
  4. 动态条形图(RunBargraph)用于数据展示
  5. java httpcomponents_java – 如何使用Apache httpcomponents从NHttpRequ...
  6. java定义子类_java定义类和子类中的方法
  7. 【干货】2021新消费品牌STEP增长方法论:品牌营销与生意增长Playbook.pdf(附下载链接)...
  8. 比特率 波特率 带宽与容量
  9. 说说Thread的interrupt()
  10. 【软件开发架构平台】CH2 Spring IoC和Bean管理
  11. js动态显示实时时间
  12. Linux系统编程:IPC信号量
  13. 笔记本无线上网设置教程(图文)
  14. 网络上经常使用的简单投票代码
  15. Android 迷你播放器
  16. 51单片机存储器原理
  17. 推荐系统 | (2) 个性化推荐系统研究热点
  18. 划重点 iOS15正式发布, 全新的通知推送系统,你必须要知道
  19. URP Lit Shader解析(2)—LitInput.hlsl
  20. 向单片机flash中烧录自定义数据的方法

热门文章

  1. 99%的人都会遇到的Python “用户环境”问题
  2. Django中类视图的几实现方式
  3. 漫步线性代数二十一——行列式引言
  4. [机器学习-Sklearn]决策树学习与总结 (ID3, C4.5, C5.0, CART)
  5. 吴恩达深度学习 —— 4.1 深层神经网络
  6. Multi-thread--Windows和Linux下通用的线程接口
  7. Windows下CodeBlocks安装及配置注意事项
  8. 方法重载与重写,返回类型
  9. 设计模式学习笔记——命令(Command)模式
  10. Matlab 图像采集工具的使用 - Image Acquisition Toolbox【IAT】 + 大恒相机的应用【1】+多个摄像头支持