概述
最近公司的项目为实现特定的业务需求,要求自行封装一个递归树形组件,毕竟这是第一次自己手写组件,特此记录一下。
公司的项目是用vue框架,我也开了一个vue例子来实现这个功能。模拟数据呢我用了mockjs来模拟后端的请求数据,具体mockjs怎么使用,这里我就不多说了,需要了解的再咨询我吧。
先来看一下效果图(忽略一下样式,哈哈)
这里的树形数据只有三层,当然,项目中是不知道有多少层,这也是为什么要用递归来实现的原因。用递归来实现最重要的一个点在于,递归何时结束,需要有一个条件来限制。既然是树形的结构数据,那就每层的数据都差不多,这就很好解决了
通过上图的数据可以发现,下一层的视图是通过children数据来进行渲染的,那children
为空的时候就代表这个分支下面没有子分支了,因此判断条件就可以设置为这个。当然children
就要根据你自己拿到的数据改不改名字了
v-if="item[keys.children] && item[keys.children].length"
而递归组件的实现很简单,知道停止的条件就可以写了,具体代码如下
<template>
<div class="hello">
<ul>
<li v-for="(item,index) in treeData" :key="index">
<div ref="lichange" >
<span class="rightClass" @click="handleClick(item)">
<span>{{ item.label }}</span>
</span>
<div class="leftClass">
<el-input v-model="item.treeNum" @change="putChange(item)"></el-input>
</div>
</div>
<ul v-show="item.isShow" v-if="item.children && item.children.length" >
<tree ref="tree" :data="item.children" />
</ul>
</li>
</ul>
</div>
</template>
到了这一步你会发现,树形结构是渲染出来了,但是默认是全部展开的,那要怎么实现像按需点击展开关闭子分支呢?其实也很简单,在上图的treeData
数据中,每一个节点都加入一个控制显示隐藏的属性,默认为false,全部隐藏,emmm,具体怎么加上这个属性就仁者见仁智者见智了。然后再设置一个点击事件
handleClick (item) {
item.isShow = !item.isShow
}
这样就实现了完整的树形组件需求了。当然这只是这个需求的最基础部分,后面的el-input才是需求的重点,具体就没必要讲了。
一个有点意思的点是,如果你要实现点击实现高亮你会怎么做?
最简单的当然是拿到全部的DOM节点做循环设置背景色样式全部为空,然后设置当前点击的DOM节点高亮,确实是很简单。但是既然是用了vue,当然想想能不能用vue的方法来实现嘛。所以这里我用的是
:class="['liBGchange', active === item.id ? 'active' : '' ]"
liBGchange
是高亮的样式名,active
是当前点击的节点数据的id
属性,如果active
与节点数据的id
相匹配就加载高亮样式。
问题在于你怎么在递归组件中向上传递当前点击的active
?我们都知道,子组件要改变父组件的值需要用到$emit
事件,那在递归组件中要怎么一层一层改变父层的active
?
简单,递归上去呗。
这里就要用到.sync+$emit
,不会的可以去百度一下,解答还是很多的。总之.sync是一个语法糖,糅合了v-bind和一个改变v-bind绑定的变量的事件。
实现过程如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQIFSLbP-1607742137709)(https://i.loli.net/2020/12/12/RoBSa39txUweIbM.png)]
在组件上绑定变量active和一个事件
点击的时候把当前点击节点的id传到父层上,父层会触发update:active事件进而继续向上传递this.$emit('update:active', item.id)
事件,直到所有的父层的active都改变了。
全部代码如下
封装的树形组件
<template>
<div class="hello">
<ul>
<li v-for="(item,index) in treeData" :key="index">
<div ref="lichange" :class="['liBGchange', active === item.id ? 'active' : '' ]" >
<span class="rightClass" @click="handleClick(item)">
<span>{{ item.label }}</span>
</span>
<div class="leftClass">
<el-input v-model="item.treeNum" @change="putChange(item)"></el-input>
</div>
</div>
<ul v-show="item.isShow" v-if="item.children && item.children.length" >
<tree ref="tree" :data="item.children" :test="test" :active="active" @update:active="$emit('update:active', $event)" />
</ul>
</li>
</ul>
</div>
</template>
调用tree树形组件
<template>
<div class="home">
<tree :data="treeData" v-if="treeData.length>0" :test.sync="treeData[0]" :active.sync="treeData[0].id"/>
</div>
</template>
特别注意调用组件的时候,这里要写对。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpA6TzVM-1607742137715)(https://i.loli.net/2020/12/12/HcWZDqNLyrvglu9.png)]
到这里就实现简单的树形递归组件了,可能我讲的过于啰嗦和混乱,还望见谅啊。