公司的树业务复杂的一批,并且数据量十分大,动不动测试就说最少测上w条数据,我顶个狒狒。php
公司的中后台系统用的是Element-UI框架,天然树组件也就是用的el-tree。css
说实话,el-tree已经很不错了,提供丰富的回调事件,属性,方法,在轻量数据渲染上能够说是不二之选。html
可是奈何半年多的项目实践,愈加以为el-tree卡顿了起来,数据愈来愈多,功能愈来愈复杂。vue
测试提这方面的BUG已经到了测必提的境界,优化树组件性能势在必行!!!node
寻找一条通往光明的道路。jquery
因而擅长找资源的我,找啊找,咦,找到一个很霸气的家伙:git
你看它牛的,轻松实现海量数据的高性能渲染。github
关键词:轻松、海量、高性能面试
还解释了为何要用这玩意:json
来来来,有不服气吗? ===》 big-tree.json
彭大师表示不服,安排!
首先把这个号称big-tree的json下载下来看看是真是假!!
大小
内容
一顿git clone操做,先是yarn,而后serve跑起来,F12按一按。
肉眼看:加载的很是快!眨眼之间!
看下dom加载:
???
确实牛逼啊!!!
可是这个vue-giant-tree就像它本身说的那样,仅仅是套了一层ztree的壳,修改下皮肤,全部的功能都是ztree的,因此我去ztree官网仔细看了一下文档。
仔细研究后,发现ztree确实牛逼!!能够在IE6运行且性能十分棒,可是它的icon图标十分的简陋,并且采用是的雪碧图方式,用绝对定位去获取每一个图标,这点不符合咱们公司的业务需求,我们要的是好看的,漂亮的,可随时更换的图标。
公司的UI小姐姐,说提供一份位置同样大小同样的雪碧图有难度,不必定能搞出来。
因而我打消了这个念头,决定修改ztree源码采用iconfont形式去得到灵活且丰富的icon配置。
牛顿曾言:我之因此这么成功,是由于我站在巨人的肩膀上!
我彭大师现在脚踩vue-giant-tree和ztree,产出了能够胜任业务复杂又要海量数据渲染的vue-magic-tree!
当当当!!!
亮眼表现:
随机渲染多种icon图表
这样,这颗参天大树就知足了业务复杂程度为O(n * n)的业务了。
介绍如下业务:
光上上面3点,el-tree退出游戏,i-tree退出游戏。
有的童鞋可能会说,el-tree和i-tree都有提供自定义渲染功能,也能实现icon图标自定义啊。这样说的同窗你能够试试,数据量多的时候,有多卡。并且通篇文章只比较初始化加载时间,根本没有比较勾选事件。咱们公司的el-tree在3000条数据而后点击全选(点击根节点的checkbox)就要卡2-3秒,每次取消、勾选、取消、勾选,卡的一匹。
不相信的话,我只能说你本身体会。
接下来看性能对比测试,不想看的同窗能够点击直达源码修改章节
规则说明:
1.一样一份文件,本地加载(ivew的节点名称必须为title, 因此iview那份数据中的name都改为了title, 看我github仓库代码能够看到iview加载的不是全局这份数据,可是是同样的。)
const bigData = require("@/mock/big-tree.json");
复制代码
2.一样适用清空缓存从新刷新。
3.gif一样的位置,大小,比率。
保证了一致性,能够明显看出,iview和element加载明显有卡顿,而ztree几乎没有延迟。
线上测试地址:测试 (海外服务器加载可能比较慢)
牛批!!
我要说下这份数据是一个一维的数组,ztree根据pid自动拼接成一颗树形结构的数据而后渲染,还这么出色!!!
并且ztree的功能异常丰富,能够知足你一切须要,你要本身看文档,结合你的业务去开发。
我在这里只是想说ztree牛逼!!!
嘿嘿,油头蓬面大叔来了
十八禁哈
既然知道这颗树如此优秀核心仍是在于ztree,那么我们就把它的开源代码下载下来,搞起来!!!
这就是ztree的源码(JQuery zTree v3.5.41)
能够看到目录结构很简单,实现ztree的都在这个js文件夹中。(因此只聚焦关注这个文件夹)
一共十一个文件,(按顺序)第一个是jquery的min版(无论不看)。而后粗略看了眼,带min的文件都给它忽略掉!
这样就剩下5个文件:
jquery.ztree.all.js
jquery.ztree.core.js
jquery.ztree.excheck.js
jquery.ztree.exedit.js
jquery.ztree.exhide.js
复制代码
从命名上看,很清晰了其实:
由于引入项目的是all文件,其实只看all就好了哈。(all包含了core和ex的全部)
动手!!!
由于我们须要改变它陈旧古老的icon节点,因此从文档入手
咱们能够找到控制节点图标的功能设置是经过一个叫作setting.view.showIcon变量控制的
setting.view.showIcon: 控制节点图标显示与否。
那么我们就看看这玩意在源码中是怎么处理逻辑的,从而找到突破口。
打开all文件:其实里面备注什么都很详细和完善。
定义了:
继续往下看:
定义了:
其实很明了,咱们想要修改icon图标所在的那个dom,因此就看view底下的方法便可;亦可用搜索showIcon关键字来找到对应方法的位置。不过先以大局观看整个项目比较好,这样脑子里有一个程序设计的完整的流程和生命周期这样的概念。
共计30-40个方法(粗略数了一下)
首先不要慌,先看函数名,相信本身可以看懂。
圈起来这个makeDOMNodeIcon,就是渲染icon和节点名称的那个方法。
makeDOMNodeIcon: function (html, setting, node) {
var nameStr = data.nodeName(setting, node),
name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
html.push("<span id='", node.tId, consts.id.ICON,
"' title='' treeNode", consts.id.ICON, " class='", view.makeNodeIcoClass(setting, node),
"' style='", view.makeNodeIcoStyle(setting, node), "'></span><span id='", node.tId, consts.id.SPAN,
"' class='", consts.className.NAME,
"'>", name, "</span>");
},
复制代码
很简单,这个方法就作了一件事,往html里面push了两个span标签,一个是用于icon图标的,一个用于显示节点名称的。
咱们须要的修改icon图标这个dom,仔细查看一下,icon这个dom的class和style分别经过makeNodeIcoClass和makeNodeIcoStyle去动态的获取。
这里我要介绍下,iconfont图标渲染形式——font-class引用:
<i class="iconfont icon-xxx"></i>
复制代码
因此咱们只须要给这个节点的icon图标dom加上这两个类便可渲染出iconfont形式的图标。
往下翻看:
先看makeNodeIcoClass:
makeNodeIcoClass: function (setting, node) {
var icoCss = ["ico"];
if (!node.isAjaxing) {
var isParent = data.nodeIsParent(setting, node);
icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0];
if (isParent) {
icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE);
} else {
icoCss.push(consts.folder.DOCU);
}
}
return consts.className.BUTTON + " " + icoCss.join('_');
},
复制代码
这个方法就像名字那样,很直白,就是return一个字符串,即一些类名。
consts.className.BUTTON
这个是常量,能够在_consts
常量列表中找到值就是:
而后icoCss
就是固定一个ico
+ open close docu
三选一拼接而成,若是设置了iconSkin
属性那么就拼上这个。 也就是获得了两个类名吧:button ico_open
这样形式的。
其实这些不重要,由于咱们将彻底完全的改写这个方法。
改写以下:
makeNodeIcoClass: function (setting, node) {
const iconName = data.nodeIcon(setting, node);
return `${consts.className.BUTTON} iconfont ${iconName}`;
}
复制代码
我们经过data集合中的一个nodeIcon方法获得iconName,而后拼接成button iconfont icon-xxx
形式。
保留consts.className.BUTTON
是由于有样式,我懒得改。这样就能够渲染出iconfont的图标了。
看下data.nodeIcon(此方法放入data集合中):
nodeIcon: function (setting, node, newName) {
const key = setting.data.key.nodeType;
if (typeof newName !== 'undefined') {
node[key] = newName;
}
return setting.data.iconMap[node[key]] || "";
},
复制代码
首先得到全局设置对象中的setting.data.key.nodeType
这个属性,而后获得node[key]=node.nodeType=0
,而后经过key:value
形式返回iconMap
的对应的那个值,最终获得此节点的icon图标类名。
节点例如:
{ id: 1, pid: 0, name: "随意勾选 1", open: true, nodeType: 0, chkDisabled: true },
复制代码
setting
对象中的属性:
在这个例子中,得到的值就是setting.data.iconMap[0] = 'iconjianyuede'
最后还作了一个判断,若是·node[key]
在iconMap
中没有对应的key
,也就是setting.data.iconMap[node[key]] = undefined
,也有相应的返回值空串,这样作的好处是,有些节点不须要图标,那么在iconMap
中就不要有相应的key
,ztree就不会渲染图标。
控制图标显示与否showIcon
相关的方法makeNodeIcoStyle:
makeNodeIcoStyle: function (setting, node) {
var icoStyle = [];
if (!node.isAjaxing) {
var isParent = data.nodeIsParent(setting, node);
var icon = (isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node[setting.data.key.icon];
if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;");
if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) {
icoStyle.push("width:0px;height:0px;");
}
}
return icoStyle.join('');
},
复制代码
里面就是经过isParent
和showIcon
一块儿控制icon图标dom的样式,若是显示就是给background设置路径,不显示就让它宽高为0,最终返回一个包含样式的字符串。
咱们的业务不须要判断父节点图标显示与否,因此不用管isParent
属性,只要配合showIcon
便可,改写以下:
makeNodeIcoStyle: function (setting, node) {
if(!node.isAjaxing) {
if (setting.view.showIcon == false
|| !tools.apply(setting.view.showIcon, [setting.treeId, node], true)
|| data.nodeIcon(setting, node) === ''
) {
const iconStyle = "display:none;"
return iconStyle
}else {
return ''
}
}
},
复制代码
若是显示返回空串,不显示返回display:none;
便可。
首先项目中引入iconfont:
// main.js
import './style/iconfont.css'
复制代码
而后引用我们魔改后的ztree源码文件all:
将它require进来,别忘了引入jquery!!!
// 引入jquery
import * as $ from "jquery";
if(!window.jQuery){
window.jQuery = $;
}
// 引入魔改版ztree
require("./lib/jquery.ztree.all");
// 调用ztree官方初始化方法,参数省略
$.fn.zTree.init()
复制代码
最后效果就是这样:
效果不错,可是在你操做的时候,好比折叠这个节点会出现,此节点已有的图标会消失的问题。 因此,还须要继续看下源码接着修改。
在view集合中继续查看函数名,能够找到:
这三个方法,前缀都是expandCollapse意思就是展开节点这一类的意思。
展开expandCollapseNode方法,往下翻到1235行:
这里用一个node.open
属性去判断调用replaceIcoClass方法,替换了icon图标dom的class。因此下一步,修改这个方法。
replaceIcoClass:
replaceIcoClass: function (node, obj, newName) {
if (!obj || node.isAjaxing) return;
var tmpName = obj.attr("class");
if (tmpName == undefined) return;
var tmpList = tmpName.split("_");
switch (newName) {
case consts.folder.OPEN:
case consts.folder.CLOSE:
case consts.folder.DOCU:
tmpList[tmpList.length - 1] = newName;
break;
}
obj.attr("class", tmpList.join("_"));
},
复制代码
就是作了替换class的操做,不分析了,直接修改:
replaceIcoClass: function (node, obj, newName) {
// 根据你的状况,我这边就所有删除就能够了
},
复制代码
改完以后,折叠的时候节点图标不会消失,正常了。这样改完全部增删改功能都不会影响图标正常的显示了。
按照上面修改后,正常使用,能够知足业务的需求并且渲染海量数据上性能也十分出色。不过也只能说暂且完成一个阶段而已,毕竟狗需求每天改。。。
我我的认为能看懂一些源码,且修改一些内容,并不厉害。毕竟会玩手机跟生产手机是一回事吗?
你们也不要妄自菲薄,其实不少源码并无你想象的那些难,只是你认为本身不行,你的潜意识一直告诉本身:你看不懂。因此致使了你们误觉得这些源码很难看懂。
其实我想告诉你们的是,多看源码,好处很是明显:
GitHub连接:vue-magic-tree 但愿能过帮到你们。 线上测试地址:测试