咋一看标题这似乎没啥难度啊,不就一个组件嵌套的事吗?事实上如果真这么简单吗?请看下文!
题目
data 数据如下,要求我们使用该数据,用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: '复制'
},
{
title: '粘贴'
}
]
}
]
方案一:递归嵌套组件
先别急,我们先按第一时间想到的方法——通过组件嵌套来试试,实现代码如下:
子组件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>
好了,运行一下,能渲染出来,咋一看似乎没什么毛病,只是看起来样子有点怪怪的。
点击一个项试试下,结果报错了!!!
ERROR: this.$parent.handlerClick is not a function
怎么回事?!!
在devtools一看,组件结构:mu-list>item>mu-item>item>mu-item
在mu-list和父级item之间,以及父级item和子级item之间都夹了一层 item 组件,子级中的this.$parent自然就指向了 item 组件,而item组件自然是没有handlerClick的实现。
这下真相大白了,是因为父级和子级组件不兼容导致出错,怪怪的样子也是这个原因引起的。这可难办了,难道必须要放弃掉muse漂亮的组件效果,自己用dom写一个嵌套组件?!先别灰心,我们来看看方案二
在这里我要提一下,可重用Vue的组件之间不建议偶合度过高,如果必须嵌套的,建议内置实现嵌套(参考element-ui 的 el-tree组件),以减少组件使用者的编码量。
方案二:render函数渲染
我们还有一条路,那就是render函数(render函数参考),Vue有了非常方面的模板之后,仍然为我们保留了类似React的渲染方式,并且其还支持jsx哦!重点是我们可以通过render函数将两个组件合并为一个组件,这样就不会产生多余的中间组件了,我们可以通过render函数来完成渲染,代码如下:
合并后的唯一组件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: {
// 创建子组件
createItem(createElement, item, slot = 'default') {
//创建子组件
const children = item.children ?
item.children.map(childItem => this.createItem(createElement, childItem, 'nested')) : []
// 插入图标组件
children.push(createElement('mu-icon', {
slot: 'left',
props: {
value: item.icon
}
))
return createElement('mu-item', {
slot: 'nested',
props: {
title: childItem.title
},
on: {
// 如果有事件绑定可以写在这里
}
}, children)
}
}
}
</script>
运行一下,正常,点击项,完全正常!一切OK!
方案三:jsx语法的render函数
也许你会觉得直接用JS来生成元素的方式,不太直观,用起来也不方便,没关系,我们可以使用React的jsx语法来编写render函数(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: {
// 创建项
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>
这样一看是不是简单易读多了呢?