前言
我们在 Vue 的官方文档其中一节 渲染函数 & JSX 中有这么一句话:『Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。』,在实际项目中还真的遇到过一些使用模板解决不了的问题,如要生成一棵无限的 Dom 树,或者生成无限层级的菜单等情况。
这些情况下,使用模板真的是束手无策,使用过 react 的同学可能就知道,使用 jsx 可以轻松使用递归实现。当然使用 Vue 也可以使用 jsx 来实现,我也尝试去安装 babel 插件,结果插件要求 node 的版本 >=4, <=9,装不成功,想想我对 h 函数 还是挺熟悉的,算了,直接使用 h 函数来解决吧。
废话不多数,先上在线 Demo,Github 上源码地址
代码分析
template代码如下:
<template>
<article class="pageview">
<header class="header fixed">
<div class="container"><a
class="back back_ico"
href="javascript:void(0);"
@click="goBack"
></a><span class="title">{{msg}}</span></div>
</header>
<section class="main">
<div
id="menu"
class="clearfix menu"
>
<node-content :nodes="data" />
</div>
</section>
</article>
</template>
这里面,主要是使用了 node-content 这个本页面使用 render(createElement)
创建的自定义组件。
JS 代码段如下:
export default {
name: 'RenderTreeDemo',
components: {
NodeContent: {
props: {
nodes: {
required: true
}
},
render (h) {
// const nodes = this.nodes
function clickHandler (e) {
// console.log(e)
let el = e.target
let nextSibling = el.nextElementSibling
if (nextSibling) {
if (el.classList.contains('open')) {
el.classList.remove('open')
el.classList.add('close')
nextSibling.classList.add('hide')
} else {
el.classList.remove('close')
el.classList.add('open')
nextSibling.classList.remove('hide')
}
}
}
function renderNode (nodes) { // 关键代码,递归生成树
if (nodes.length > 0) {
return h('ul', {}, nodes.map((_) => {
return h('li', { key: _.id }, [
h('a', {
'class': {
name: true,
open: _.children.length > 0
},
attrs: {
href: 'javascript:void(0);'
},
on: {
click: clickHandler
}
}, [_.name])
].concat(
_.children.length > 0 ? renderNode(_.children) : []
))
}))
}
}
return this.nodes.length > 0 ? renderNode(this.nodes) : h()
}
}
},
data () {
return {
msg: '渲染函数 Tree Demo',
data: [
{
id: 0,
name: '节点1',
children: [
{
id: 1,
name: '节点1-1',
children: []
},
{
id: 2,
name: '节点1-2',
children: [
{
id: 7,
name: '节点1-2-1',
children: [
{
id: 10,
name: '节点1-2-1-1',
children: []
},
{
id: 11,
name: '节点1-2-1-2',
children: []
}
]
},
{
id: 8,
name: '节点1-2-2',
children: []
}
]
}
]
},
{
id: 3,
name: '节点2',
children: [
{
id: 4,
name: '节点2-1',
children: []
},
{
id: 5,
name: '节点2-2',
children: []
}
]
},
{
id: 6,
name: '节点3',
children: []
}
]
}
},
methods: {
goBack: goBack
}
}
主要的思路:既然模板实现不了递归生成树,我们要使用 js 实现主要有三种选择,要么在 render
函数中使用 jsx;要么直接使用 render(createElement)
渲染函数带进来的 createElement
函数,其实就是 h
函数来实现递归生成;还有最后一种,就把自已做成一个节点组件,然后递归调用自己渲染循环数据,请稳步《Vue 中递归调用节点组件自身实现无限节点的树》。
扩展
实际应用中,我们往往需要扩展许多功能,如:对树接点绑定一些 Dom 操作的交互效果,对树节点增删改查,拖放改动节点结构等等。在 Vue 中有两种思路,要么通过 js 直接操作 dom,要么通过操作数据驱动 Dom 节点的变化。一般要结合着来使用与进行。感兴趣的同学可以通过深度学习 Element UI 的 Tree 组件。