原本是不想发出来的,可是呢,最后出于装逼,讨论和分享的想法下仍是拿出来。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团队开源代码