因为公司业务需求,须要开发一个展现组织架构的树组件(公司的项目是基于Vue)。在GitHub上找了半天,这类组件很少,也没有符合业务需求的组件,因此决定本身造轮子!javascript
那么,问题来了。递归组件怎么写?css
Vue官方文档是这样说的:html
组件在它的模板内能够递归地调用本身。不过,只有当它有 name 选项时才能够这么作vue
接下来,咱们来写一个树节点递归组件:java
<template>
<div class="org-tree-node">
<div class="org-tree-node-label">{{data.label}}</div>
<div class="org-tree-node-children" v-if="data.children">
<org-tree-node v-for="node in data.children" :data="node" :key="data.id"></org-tree-node>
</div>
</div>
</template>
<script> export default { name: 'OrgTreeNode', props: { data: Object } } </script>
<style> /* ... */ </style>
复制代码
而后渲染这个这个组件,效果以下node
至此,一个简单的组织架构树组件就完成了。git
然而,事情还远远没有结束。。。github
需求说:节点的label要支持定制,树要支持水平展现!element-ui
所以,咱们对递归组件做以下修改:数据结构
<template>
<div class="org-tree-node">
<div class="org-tree-node-label">
<slot>{{data.label}}</slot>
</div>
<div class="org-tree-node-children" v-if="data.children">
<org-tree-node v-for="node in data.children" :data="node" :key="data.id"></org-tree-node>
</div>
</div>
</template>
<script> export default { name: 'OrgTreeNode', props: { data: Object } } </script>
<style> /* ... */ </style>
复制代码
咱们使用slot插槽来支持label可定制,可是问题又来了:咱们发现只有第一层级的节点label能定制,嵌套的子节点不能有效的传递slot插槽。上网查了半天,仍然没有结果,因而再看官方文档。发现有个函数式组件。因为以前使用过element-ui
的tree
组件,受到启发,就想到了能够像element-ui
的tree
组件同样传一个renderContent
函数,由调用者本身渲染节点label,这样就达到了节点定制的目的!
接下来,咱们将树节点模板组件改形成函数式组件。编写node.js:
首先咱们实现一个render函数
export const render = (h, context) => {
const {props} = context
return renderNode(h, props.data, context)
}
复制代码
实现renderNode函数
export const renderNode = (h, data, context) => {
const {props} = context
const childNodes = []
childNodes.push(renderLabel(h, data, context))
if (props.data.children && props.data.children.length) {
childNodes.push(renderChildren(h, props.data.children, context))
}
return h('div', {
domProps: {
className: 'org-tree-node'
}
}, childNodes)
}
复制代码
实现renderLabel函数。节点label定制关键在这里:
export const renderLabel = (h, data, context) => {
const {props} = context
const renderContent = props.renderContent
const childNodes = []
// 节点label定制,由调用者传入的renderContent实现
if (typeof renderContent === 'function') {
let vnode = renderContent(h, props.data)
vnode && childNodes.push(vnode)
} else {
childNodes.push(props.data.label)
}
return h('div', {
domProps: {
className: 'org-tree-node-label'
}
}, childNodes)
}
复制代码
实现renderChildren函数。这里递归调用renderNode,实现了递归组件
export const renderChildren = (h, list, context) => {
if (Array.isArray(list) && list.length) {
const children = list.map(item => {
return renderNode(h, item, context)
})
return h('div', {
domProps: {
className: 'org-tree-node-children'
}
}, children)
}
return ''
}
复制代码
至此咱们的render函数完成了,接下来使用render函数定义函数式组件。在tree组件里面声明:
<template>
<!-- ... -->
</template>
<script> import render from './node.js' export default { name: 'OrgTree', components: { OrgTreeNode: { render, // 定义函数式组件 functional: true } } } </script>
复制代码
至此咱们的函数式组件改造完成了,至于水平显示用样式控制就能够了。
样式使用less预编译。节点之间的线条采用了 :before
、:after
伪元素的border
绘制
labelClassName
属性,以支持对节点label的样式定制labelWidth
属性,用于限制节点label的宽度props
属性,参考element-ui
的tree
组件的props属性,以支持复杂的数据结构collapsable
属性,以支持子节点的展开和折叠(展开和折叠操做需调用者实现)刚开始采用了
flex
布局,可是要兼容IE9,后来改为了display: table
布局
default
horizontal
最后附上源码传送门:github.com/hukaibaihu/… !