因爲公司業務需求,須要開發一個展現組織架構的樹組件(公司的項目是基於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/… !