原本是不想發出來的,可是呢,最後出於裝逼,討論和分享的想法下仍是拿出來。css
並且我對於樹形組件的自定義節點這塊,仍是沒有理解透徹。也但願有大神幫忙解惑。html
而後其實個人眼界仍是有限,一直都停留在ui組件上面,可是做爲一個普通的前端,你們不都幹這樣的事情嗎?既然你以爲我眼界小,那麼請說說你在幹什麼。(一次偏激的回覆)前端
感慨:vue
若是沒有網絡的世界我想我會抑鬱而死。其實做家不是由於他想看成家,他是爲了發泄本身心中的不快。爲何網上肆無忌憚,由於沒有生活中那麼多的指責。如今你們都活成了人面獸心的人了吧。node
說明:web
我不是一個大神,我真實的寫代碼的經歷只有一年,一開始是微信小程序開發,後面年初的時候開始寫的web網頁。我不知道爲何,我只有一年的代碼經歷,可是卻超過了不少人(一樣也不如不少人)。可是在這樣一個社會環境和南京這樣一個哪怕是互聯網不怎麼地的環境下,我依舊是一個菜鳥。並且我是一個耿直的人,因此我喜歡直抒胸臆,可是這個社會不讓,生活不讓。很苦惱。小程序
遞歸:本身調用本身微信小程序
寫樹形組件確定是能夠無限嵌套的,我記得以前我寫過一篇關於遞歸組件的文章,可是那個是兩個組件相互調用從而實現樹形組件的遞歸。bash
此次不同,是一個比較省事的方式來寫。更少的代碼。微信
<template>
<div class="dht-tree-main">
<twig-node
v-for="(item, index) in data"
:key="getNodeKey(item, index)"
:child="item"
:level="1"
:data-location="[1, index]"
:indent="indent"
>
</twig-node>
</div>
</template>
<script>
export default {
name: "dhtTree",
components: {
twigNode: () => import("./twig-node.vue")
},
props: {
data: {
type: Array
},
indent: {
// 每一層縮進多少像素
type: Number,
default: 18
}
},
data() {
return {
active: true
};
},
beforeCreate() {},
created() {
this.isTree = true;
},
beforeMount() {},
mounted() {
//是否有子元素做爲模板
this.slot = !!this.$scopedSlots.default;
},
destroyed() {},
methods: {
getNodeKey(node, index) {
return node.id ? node.id : index;
}
}
};
</script>
複製代碼
這裏其實很簡單,就是寫一個容器,用來存放你的遞歸組件。
參數解析:
:key="getNodeKey(item, index)"
:child="item"
:level="1"
:data-location="[1, index]"
:indent="indent"
複製代碼
key:這個這樣寫的意義在於讓客戶能夠自定義key值
child:子節點數據
level:層級,在main下是定義初始層級
data-location:用於表示每個節點的位置,也就是定位的做用
indent:節點是須要縮進的,定義一個初始縮進值,後面將按這個計算每一層須要縮進多少
這裏我會逐步拆分的來說
先放所有的html部分
<div class="dht-tree-twig-one">
<div
class="dht-tree-node-content"
:style="{ paddingLeft: level * indent + 'px' }"
@click="showNode"
>
<!--箭頭-->
<span
v-if="child.children.length > 0"
class="iconfont icon-jiantou arrow"
:style="{ transform: 'rotate(' + rotate + 'deg)' }"
></span>
<!--icon圖標-->
<span v-if="child.icon" :class="child.icon" class="icon"></span>
<!--可自定義部分-->
<node-content :node="child"></node-content>
</div>
<transition-group name="dht-tree-node">
<twig-node
v-for="(item, index) in child.children"
v-show="isShow"
:key="getNodeKey(item, index)"
:child="item"
:level="level + 1"
:data-location="[level + 1, index]"
:indent="indent"
></twig-node>
</transition-group>複製代碼
這裏主要分爲兩部分,在transition-group上面爲當前節點展現的效果,而其中的部分是組件的遞歸部分。
<div
class="dht-tree-node-content"
:style="{ paddingLeft: level * indent + 'px' }"
@click="showNode"
>複製代碼
這裏我很簡單,就是按層級計算縮進的像素,而後算就好了。
這個click函數是當前節點關閉或者打開。這塊後面須要單獨展開
做用域插槽部分請看vue文檔:
https://cn.vuejs.org/v2/guide/components-slots.html#做用域插槽
<!--箭頭-->
<span
v-if="child.children.length > 0"
class="iconfont icon-jiantou arrow"
:style="{ transform: 'rotate(' + rotate + 'deg)' }"
></span>
<!--icon圖標-->
<span v-if="child.icon" :class="child.icon" class="icon"></span>
<!--可自定義部分-->
<node-content :node="child"></node-content>複製代碼
這裏的箭頭還有icon都是修飾做用,意義不大。核心在於
node-content這個組件,這個組件是jsx組件。說實在,這塊我看了半天仍是對於elementUI中的一些東西不理解。
這是組件的實現:
components: {
nodeContent: {
props: {
node: {
required: true
}
},
render(ce) {
const parent = this.$parent;
const tree = parent.tree;
const node = this.node;
// return ce("span", node.label);
// console.log(tree);
if (tree.slot) {
return tree.$scopedSlots.default
? tree.$scopedSlots.default({ node })
: (parent.$scopedSlots = {
default: () => {
return node;
}
});
} else {
return ce("span", node.label);
}
}
}
},複製代碼
這裏很關鍵在於須要判斷是不是父節點,是不是當前節點的父節點,而後設置$scopedSlots。這樣就是把組件自己設置成了做用域插槽。不過說實在,我這塊代碼不是很理解。我是參考elementUI部分,寫出來的。(但願看過elementUI源碼的,或者懂的人能給我解惑下。)
這塊我只有一個模糊的概念,應該這麼寫,可是我不能自信的寫出來。
內部拆解
三個聲明:
parent 父節點
tree 一級節點,也就是第一菜單
node 當前傳入的數據編輯數據
if (tree.slot) {
return tree.$scopedSlots.default
? tree.$scopedSlots.default({ node })
: (parent.$scopedSlots = {
default: () => {
return node;
}
});
} else {
return ce("span", node.label);
}複製代碼
這裏的if部分是判斷一級菜單是否有編寫自定義的節點數據。
slot的判斷語句是這樣的,在main組件下的mouted下面
this.slot = !!this.$scopedSlots.default;複製代碼
而後若是有自定義的節點數據,那麼渲染的時候就根據當前是哪一級的節點進行做用域數據渲染。
這裏我最不理解的是,我明明自定義的節點數據在main組件下,可是渲染的時候卻能夠實現每一級遞歸的數據都變成同樣的節點。明明自定義的節點數據在外層啊。望大神解惑
這個比較簡單,可是處理方式有兩種。
第一種:這種很麻煩,須要層層遞遞歸計算當前的菜單開啓關閉的高度。
第二種:利用vue的transition-group組件,包裹你的遞歸組件,必要的時候開啓關閉就好了
<transition-group name="dht-tree-node">
<twig-node
v-for="(item, index) in child.children"
v-show="isShow"
:key="getNodeKey(item, index)"
:child="item"
:level="level + 1"
:data-location="[level + 1, index]"
:indent="indent"
></twig-node>
</transition-group>複製代碼
看v-show,就這麼簡單了。
一、先說性能
這是我剛寫博客的時候,一位掘金大神(好像仍是一個漂亮妹子)提供的。
掘金暱稱:無恙道別
他說:在自定義組件渲染的時候(也就是我剛纔的那段jsx語法)若是,渲染數據超過100條會感受到卡頓,500條就會很是明顯,可是若是是換成普通的html元素就不會卡頓。
因此,本身會寫組件也很重要的。
二、代碼部分
<template>
<div class="dht-tree-twig-one">
<div
class="dht-tree-node-content"
:style="{ paddingLeft: level * indent + 'px' }"
@click="showNode"
>
<!--箭頭-->
<span
v-if="child.children.length > 0"
class="iconfont icon-jiantou arrow"
:style="{ transform: 'rotate(' + rotate + 'deg)' }"
></span>
<!--icon圖標-->
<span v-if="child.icon" :class="child.icon" class="icon"></span>
<!--可自定義部分-->
<node-content :node="child"></node-content>
</div>
<transition-group name="dht-tree-node">
<twig-node
v-for="(item, index) in child.children"
v-show="isShow"
:key="getNodeKey(item, index)"
:child="item"
:level="level + 1"
:data-location="[level + 1, index]"
:indent="indent"
></twig-node>
</transition-group>
</div>
</template>
<script>
export default {
name: "twigNode",
components: {
nodeContent: {
props: {
node: {
required: true
}
},
render(ce) {
const parent = this.$parent;
const tree = parent.tree;
const node = this.node;
// return ce("span", node.label);
// console.log(tree);
if (tree.slot) {
return tree.$scopedSlots.default
? tree.$scopedSlots.default({ node })
: (parent.$scopedSlots = {
default: () => {
return node;
}
});
} else {
return ce("span", node.label);
}
}
}
},
data() {
return {
tree: null,
rotate: 0, // 三角形標記
isShow: false //操做子元素關閉
};
},
props: {
indent: {
// 縮進
type: Number,
default: 18
},
dataLocation: Array, //數據定位,表示層級和數據位置
level: Number, //當前層級
child: Object //子節點數據
},
beforeCreate() {},
created() {
const parent = this.$parent;
if (parent.isTree) {
this.tree = parent;
} else {
this.tree = parent.$parent.tree;
}
/*if (this.child.children.length > 0 || this.level === 1) {
this.isShow = true;
}*/
},
beforeMount() {},
mounted() {},
destroyed() {},
methods: {
getNodeKey(node, index) {
return node.id ? node.id : index;
},
//打開或者關閉節點
showNode() {
if (this.child.children.length <= 0) return false;
//操做子元素方式開啓關閉
if (this.isShow) {
this.isShow = false;
this.rotate = 0;
} else {
this.isShow = true;
this.rotate = 90;
}
}
}
};
</script>複製代碼
我其實一開始寫組件庫的時候css都是寫在每一個組件後面的,有些人會直接用scope。
直到我寫遞歸組件我想到一個問題,vue在解析的時候每次都會解析一次css,那麼就會致使你遞歸多少次,css加載屢次。若是是scope的狀況下,那麼就比較崩潰了。會很是多重複的css出現。特別是寫組件庫的時候,你千萬別用scope,會很容易致使連樣式穿透也沒法修改css,致使自定義性不好。
因此我如今把個人組件庫的css所有單獨抽出來了,用一個index.scss文件統一加載
下面是遞歸組件的css部分
關於scss變量部分請本身換成css屬性
.dht-tree-main {
position: relative;
}
.dht-tree-twig-one {
position: relative;
//transition: height 0.5s ease;
overflow: hidden;
.dht-tree-node-content {
display: flex;
flex-flow: row;
align-items: center;
padding-left: 18px;
height: 25px;
overflow: hidden;
&:hover,
&:active {
background: rgba(15, 128, 255, 0.1);
}
.arrow {
font-size: 12px;
color: $font_info;
margin-right: 5px;
transition: transform 0.5s ease;
//transform: rotate(90deg);
}
.icon {
font-size: $size-main;
color: $icon-main;
margin-right: 5px;
}
}
.dht-tree-node-enter-active, .dht-tree-node-leave-active {
transition: opacity .5s;
}
.dht-tree-node-enter, .dht-tree-node-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
}複製代碼
<dht-tree :data="data">
<span slot-scope="{ node }" class="dhtceshi">{{ node.label }}</span>
</dht-tree>複製代碼
data數據:裏面沒有icon,注意icon是css類
data: [
{ id: 2, label: "第2個", children: [] },
{
id: 1,
label: "第1個",
children: [
{
id: 1,
label: "二級第1個",
children: [
{
id: 1,
label: "三級1第1個",
children: [
{ id: 1, label: "1", children: [] },
{ id: 2, label: "2", children: [] },
{ id: 3, label: "3", children: [] },
{ id: 4, label: "4", children: [] },
{ id: 5, label: "5", children: [] }
]
},
{ id: 2, label: "三級2第2個", children: [] },
{ id: 3, label: "三級3第3個", children: [] },
{ id: 4, label: "三級4第4個", children: [] },
{ id: 5, label: "三級5第5個", children: [] }
]
},
{ id: 2, label: "二級第2個", children: [] },
{ id: 3, label: "二級第3個", children: [] },
{ id: 4, label: "二級第4個", children: [] },
{ id: 5, label: "二級第5個", children: [] }
]
},
{ id: 3, label: "第3個", children: [] },
{ id: 4, label: "第4個", children: [] },
{ id: 5, label: "第5個", children: [] }
]複製代碼
本文已經把源碼徹底貼出來了,main組件部分在一開始,遞歸子組件在最後部分
使用是單獨貼出來的第五點。
感謝elementUI團隊開源代碼