大概因为平时工作项目的原因,写了很多次树形组件,越写越觉得可以写得更简单并且更具有复用性、扩展性。树组件的应用场景很多,比如一篇文章的目录、一个公司部门组织情况、思维导图等,其实都可以用树形结构来描述。本文讲述一下vue中树组件的简单实现。
树组件在线体验地址:http://wintc.top/laboratory/#/tree。
树形数据是指形如以下的数据结构:
[
{
id: '1',
title: '节点1'
children: [
{
id: '1-1',
title: '节点1-1'
},
{
id: '1-2',
title: '节点1-2'
}
]
},
{
id: '2',
title: '节点2',
children: [
{
id: '2-1',
title: '节点2-1'
},
{
id: '2-2',
title: '节点2-2'
}
]
},
{
id: '3',
title: '节点3'
}
]
这并不是《数据结构与算法》里严格意义上的树定义,严格的树定义,第一层应该是一个根节点,而此处的第一层就包含了多个节点。不过这不重要,实用为上,这样的结构或许更加通用(试想,如果第一层是只包含一个节点的树,和标准定义的树又有什么本质区别呢)。
树结构是递归的,它可能有很多级,我们在渲染树结构的时候也采用递归的方式来渲染。
或许我们在平常开发的过程中都很少使用这个属性(至少我是如此),不过这个属性却有两个很重要的作用,摘自Vue官网:
允许组件模板递归地调用自身。注意,组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name。
指定 name 选项的另一个好处是便于调试。有名字的组件有更友好的警告信息。另外,当在有 vue-devtools,未命名组件将显示成 <AnonymousComponent>,这很没有语义。通过提供 name 选项,可以获得更有语义信息的组件树。
这里我们因为要递归地渲染树形结构,即渲染树节点的时候使用当前组件作为子组件,所以会用到name属性。
Vue插槽借鉴于Web components,是在父组件自定义实现子组件部分dom的一个方法,这大大提高了子组件的复用性和扩展性。下面是一个简单的插槽使用例子。
子组件:
<template>
<div>
子组件
<slot name="custom-content"></slot>
<slot></slot>
</div>
</template>
父组件:
<child>
<template v-slot:custom-content>
<div>父组件通过插槽插入的内容——具名插槽</div>
</template>
<template v-slot:default>
父组件通过默认插槽插入的内容——默认插槽
</template>
</child>
渲染结果:
父组件可以通过v-slot提供一些插槽来达到组件内容自定义的目的,只要子组件模板里预留了这些插槽的位置。插槽可以有名字作为唯一标识,如也可以不设置名字而使用默认值default;父组件中填充插槽使用v-slot:name填充对应的插槽,name表示填充的插槽名称,未指定名字的插槽填充时使用v-slot:default或v-slot,二者作用是相同的。
关于Vue插槽,官网描述很清楚了,这里不再赘述。此处介绍几种插槽的特殊用法。
作用域插槽
默认情况下,父组件模板里通过插槽插入的部分只能访问父组件上下文,如果你需要访问子组件的部分属性,可以通过作用域插槽:在子组件中给slot传递一些prop,然后在父组件插入内容时使用这些prop,示例如下。
子组件:
<template>
<div>
子组件
<slot :nodeData="nodeData"></slot>
</div>
</template>
<script>
export default {
data () {
return {
nodeData: {
title: '节点1'
}
}
}
}
</script>
父组件:
<template>
<div id="app">
<child>
<template v-slot:default="someProp">
子组件传到插槽的nodeData的title:{{ someProp.nodeData.title }}
</template>
</child>
</div>
</template>
someProp是可以解构的,可以把从子组件传递给插槽的prop解构出来直接使用: v-slot:default="{ nodeData }"。
插槽函数
从Vue2.6.0开始,所有的插槽(包括作用域插槽和普通插槽)都会作为一个函数,并通过vm.$scopeSlots暴露出来,比如上述父组件模板中给子组件插入了一个作用域插槽(名字为默认的default),在子组件实例的$scopeSlots就会暴露对应的函数:
传递相应的参数(比如nodeData)调用这个函数,就可以得到插槽对应的虚拟DOM即VNode,在render函数中非常好使。只要你能访问到这个函数,就可以生成对应的虚拟DOM(并不是非得在当前组件),接下来在树组件叶子节点的渲染中,会用到这个用法。
作为一个可复用的树节点,渲染出来的树结构应该是可以自定义的,同时对于传入组件的prop尽可能简单。这里设计的prop仅传入一个树形数据treeData,对于每一个节点,使用作用域插槽提供每一个节点的数据来调用者自定义渲染。所以组件调用形式很简单:
<tree :treeData="treeData" ref="tree">
<template v-slot="{ nodeData }">
<!-- 树节点自定义内容,比如展开折叠按钮,节点文本,节点菜单等 -->
<div class="node-content">
{{ nodeData.title }}
</div>
</template>
</tree>
这里的调用仅仅把节点的文本渲染出来了,通常结合具体的场景,可能我们会有更多的功能需求,比如展开折叠,删除节点,添加节点等,给组件添加一个ref属性,我们可以方便地调用组件提供的函数。
通常递归实现树组件会分为两个部分:
下面直接给出树组件、节点组件的简单代码实现如下:
树组件tree.vue:
<template>
<div class="tree-node-container">
<node-content></node-content>
<div
class="tree-node-children"
:style="{
paddingLeft: indent
}"
v-if="nextShow">
<tree-node
v-for="(child, idx) of nodeData.children"
:nodeData="child"
:indent="indent"
:key="idx">
</tree-node>
</div>
</div>
</template>
<script>
export default {
name: 'tree-node',
props: {
nodeData: {
type: Object,
required: true
}
},
components: {
'node-content': {
render (h) {
let slot = this.$parent.tree.$scopedSlots.default
let { nodeData, parentData, level, nextShow } = this.$parent
return (slot ? slot({ parentData, data: nodeData, level, nextShow }) : '<div>未定义插槽内容</div>')
}
}
},
data () {
return {
tree: false,
level: 0,
parentData: null,
childrenShow: true,
indent: undefined
}
},
computed: {
nextShow () {
return this.nodeData.children && this.nodeData.children.length && this.childrenShow
}
},
created () {
let parent = this.$parent
if (parent.isTree) {
this.level = 1
} else {
this.level = parent.level + 1
this.parentData = parent.nodeData
}
while (parent && !parent.isTree) {
parent = parent.$parent
}
this.tree = parent
this.indent = this.tree.indent
this.tree.registerNodeComponent(this.nodeData.id, this)
},
beforeDestroy () {
this.tree.removeNodeComponent(this.nodeData.id)
},
methods: {
showChildren (show) {
this.childrenShow = show
}
}
}
</script>
节点组件tree-node.vue:
<template>
<div class="tree-container">
<tree-node
v-for="(nodeData, idx) of treeData"
:nodeData="nodeData"
:key="idx">
</tree-node>
</div>
</template>
<script>
import treeNode from './tree-node'
export default {
components: {
treeNode
},
props: {
treeData: {
type: Array,
requied: true
},
indent: {
type: String,
default: '20px'
}
},
data () {
return {
isTree: true,
level: 0,
componentMap: {}
}
},
methods: {
registerNodeComponent (id, component) {
this.componentMap[id] = component
},
removeNodeComponent (id) {
this.componentMap[id] = undefined
},
showChildren (id, show) {
this.componentMap[id] && this.componentMap[id].showChildren(show)
}
// 更多功能
}
}
</script>
<style lang="stylus" scoped>
.tree-container
text-align left
</style>
有几个点值得注意:
上述提供的树组件比较简单,仅仅提供了渲染树形结构、展开/折叠等功能,但是作为一个示例性的树形组件,它还是具有很强地扩展性以及复用性。因为使用Vue,我们用数据控制视图,所以你在使用的过程中想给组件增加比如节点多选、增删节点等操作,其实非常简单,你只用修改你的树形数据,并且在插槽上做相应的视图处理即可(比如增加选择框、添加下级/删除节点等菜单)。
具体使用代码可以在github查看,或在线体验:http://wintc.top/laboratory/#/tree。
原文:http://wintc.top/article/13
Js二叉树排序实现:1初始化二叉树,2二叉树的遍历,3查找最小值,4查找最大值,5删除节点
当变量指向一个对象的时候,实际指向的是存储地址,数组转树的方式:第一次遍历将数组转节点对象,存储到新的对象里,id为键值方便索引,第二次遍历根据索引插入子节点
计算机科学中最常用和讨论最多的数据结构之一是二叉搜索树。这通常是引入的第一个具有非线性插入算法的数据结构。二叉搜索树类似于双链表,每个节点包含一些数据,以及两个指向其他节点的指针;它们在这些节点彼此相关联的方式上有所不同
JS 将有父子关系的平行数组转换成树形数据:方法一:双重遍历,一次遍历parentId,一次遍历id == parendId;该方法应该能很容易被想到,实现起来也一步一步可以摸索出来;
节点的高度和平衡因子;节点高度:从节点到任意子节点的彼岸的最大值。这个相对来说容易理解。那么获得节点高度的代码实现如下:平衡因子:每个节点左子树高度和右子树高度的差值。该值为0 、 -1、 1 时则为正常值
在编写树形组件时遇到的问题:组件如何才能递归调用?递归组件点击事件如何传递?组件目录及数据结构;在组件模板内调用自身必须明确定义组件的name属性,并且递归调用时组件名称就是name属性
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构
二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
最近项目又频繁需要对扁平结构进行树形转换,这个算法从我最早接触的时候使用了递归,到现在的单次循环完成,简单记录一下算法的演变,算是对树形算法的一个简单记录,这种类型的算法在项目中的使用挺多的
经常有同学问树结构的相关操作,也写了很多次,在这里总结一下JS树形结构一些操作的实现思路,并给出了简洁易懂的代码实现。本文内容结构大概如下:
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!