公司的樹業務複雜的一批,並且數據量十分大,動不動測試就說最少測上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 但願能過幫到你們。 線上測試地址:測試