咋一看標(biāo)題這似乎沒啥難度啊,不就一個組件嵌套的事嗎?事實上如果真這么簡單嗎?請看下文!
題目
data 數(shù)據(jù)如下,要求我們使用該數(shù)據(jù),用mu-list渲染成為一個樹。
// 文件data.js
export default [
{
title: '文件',
icon: 'file',
children: [
{
title: '打開文件',
icon: 'open',
command: 'openFile',
children: [
{
title: '打開最近使用的文件'
}
]
},
{
title: '打開項目',
icon: 'open',
command: 'doIt'
}
]
},
{
title: '編輯',
children: [
{
title: '剪切'
},
{
title: '復(fù)制'
},
{
title: '粘貼'
}
]
}
]
方案一:遞歸嵌套組件
先別急,我們先按第一時間想到的方法——通過組件嵌套來試試,實現(xiàn)代碼如下:
子組件Item.vue
<template>
<mu-list-item :title="data.title">
<mu-icon slot="left" :value="data.icon"/>
<item v-for="childItem in data.children" :data="childItem" slot="nested" />
</mu-list-item>
</template>
<script>
export default {
name: 'item',
props: [
'data'
]
}
</script>
父組件Container.vue
<template>
<mu-list>
<item v-for="item in data" :data="item"/>
</mu-list>
</template>
<script>
import Item from './Item.vue'
import data from './data.js'
export default {
data() {
return {
data
}
},
components: {
item
},
props: [
data
]
}
</script>
好了,運(yùn)行一下,能渲染出來,咋一看似乎沒什么毛病,只是看起來樣子有點(diǎn)怪怪的。
點(diǎn)擊一個項試試下,結(jié)果報錯了?。?!
ERROR: this.$parent.handlerClick is not a function
怎么回事???!
在devtools一看,組件結(jié)構(gòu):mu-list>item>mu-item>item>mu-item
在mu-list和父級item之間,以及父級item和子級item之間都夾了一層 item 組件,子級中的this.$parent自然就指向了 item 組件,而item組件自然是沒有handlerClick的實現(xiàn)。
這下真相大白了,是因為父級和子級組件不兼容導(dǎo)致出錯,怪怪的樣子也是這個原因引起的。這可難辦了,難道必須要放棄掉muse漂亮的組件效果,自己用dom寫一個嵌套組件?!先別灰心,我們來看看方案二
在這里我要提一下,可重用Vue的組件之間不建議偶合度過高,如果必須嵌套的,建議內(nèi)置實現(xiàn)嵌套(參考element-ui 的 el-tree組件),以減少組件使用者的編碼量。
方案二:render函數(shù)渲染
我們還有一條路,那就是render函數(shù)(render函數(shù)參考),Vue有了非常方面的模板之后,仍然為我們保留了類似React的渲染方式,并且其還支持jsx哦!重點(diǎn)是我們可以通過render函數(shù)將兩個組件合并為一個組件,這樣就不會產(chǎn)生多余的中間組件了,我們可以通過render函數(shù)來完成渲染,代碼如下:
合并后的唯一組件Tree.vue
// Tree.vue
<script>
import data from './data.js'
export default {
data() {
return {
data
}
},
render(createElement) {
const items = this.data.map(item => this.createItem(createElement, item))
return createElement('mu-list', items)
},
methods: {
// 創(chuàng)建子組件
createItem(createElement, item, slot = 'default') {
//創(chuàng)建子組件
const children = item.children ?
item.children.map(childItem => this.createItem(createElement, childItem, 'nested')) : []
// 插入圖標(biāo)組件
children.push(createElement('mu-icon', {
slot: 'left',
props: {
value: item.icon
}
))
return createElement('mu-item', {
slot: 'nested',
props: {
title: childItem.title
},
on: {
// 如果有事件綁定可以寫在這里
}
}, children)
}
}
}
</script>
運(yùn)行一下,正常,點(diǎn)擊項,完全正常!一切OK!
方案三:jsx語法的render函數(shù)
也許你會覺得直接用JS來生成元素的方式,不太直觀,用起來也不方便,沒關(guān)系,我們可以使用React的jsx語法來編寫render函數(shù)(jsx參考),按照jsx使用文檔上的說明,只需要簡單幾步就可以將上面的例子改造成簡潔易與讀寫的代碼。
// Tree.vue
<script>
import data from './data.js'
export default {
data() {
return {
data
}
},
render(createElement) {
return <mu-list>
this.data.map(item => this.createItem(item, 'default'))
</mu-list>
},
methods: {
// 創(chuàng)建項
createItem(item, slot = 'default') {
return <mu-item
slot={slot},
title={childItem.title}>
<mu-icon slot="left" value={item.icon} />
item.children && item.children.map(childItem => this.createItem(childItem, 'nested')) : []
</mu-item>
}
}
}
</script>
這樣一看是不是簡單易讀多了呢?