记一个Vue渲染函数render的使用场景

3,612 阅读3分钟

背景

最近在弄一个可视化后台平台,需要实现一个递归组件,已是便使用template模板写了一个loopComponent.vue的递归组件,写完之后发现并不能满足需求,原因主要是因为使用template模板在递归时总会多出一个父级容器,请往下看例子。

template版递归组件

  • LoopComponent.vue文件 请留意下面的class="aaaaaaaaaaaa"
<template>
  <!-- 注意此处的aaaaaaaaaaaaa 待会儿会说到 -->
  <div class="aaaaaaaaaaaaa">
    <div v-for="(item, index) in list" :key="index" class="list-item">
      <div class="item-name">
        <span>{{ item.name }}</span>
      </div>
      <template v-if="item.children">
        <loop-component :list="item.children" class="children-item" />
      </template>
    </div>
  </div>
</template>
<script>
export default {
  name: "loopComponent",
  props: {
    list: Array
  }
};
</script>
  • main.vue 文件
<template>
    <div class="list-detail">
        <loop-component :list="list"></loop-component>
    </div>
</template>
<script>
    export default {
        list: [{
          name: "广东",
          children: [{
              name: "广州",
              children: [{
                  name: "天河区"
                },
                {
                  name: "黄浦区"
                }]
            },{
              name: "深圳",
              children: [{
                  name: "福田区"
                },
                {
                  name: "南山区"
                }]
            }]
        }]
    }
</script>
  • 渲染后的页面如下

    image.png

从渲染的页面来上看着似乎没什么问题,但是事实是使用template模板渲染的html结构上却会存在一些弊端,请记住上面标注的class="aaaaaaaaaaaa",现在让我们F12看看它在哪里。

  • F12下渲染后的html结构

image.png 从截图可以看出,class="aaaaaaaaaaaa"出现了三次。递归组件在每层次递归都会把最外层的div添加进去,虽然看着多了一层没什么问题,但是随着一些有使用到slot插槽的组件时,在父子组件或同级组件会有数据间的关联,如果使用template版递归组件的话,由于多了一层div,原本渲染后的元素层级由父子或同级层级变成了爷孙层级,从而导致一些意想不到的隐藏BUG(找起来真要命,亲身经历。。。),这不是我们想要的。有机智的小伙伴可能会想到,把外层<div class="aaaaaaaaaaaaa">去掉,这样的话,Vue会给你一个大大的提示:Cannot use v-for on stateful component root element because it renders multiple elements,翻译过来就是不能再根元素使用v-for指令。其实原因也很简单,在Vue2版本里,template模板就只能存在一个根元素,随着只写了一个div,但是有了v-for指令就是有多个根元素了,所以是不允许的(Vue3除外哈哈哈)。

image.png

想要解决这个问题,就需要引入我们的主角渲染函数render了。

渲染函数render

什么是渲染函数render? 其实在Vue的官方文旦就已经说的很详细了 cn.vuejs.org/v2/guide/re…

Vue模板编译

在大多数开发场景下,我们都是使用Vue提供的模板语法来声明式地描述状态和DOM之间的绑定关系,然后通过模板来生成真实DOM并呈现在浏览器上。其中vue在拿到我们的模板时,会将其编译成渲染函数,最后执行完渲染函数就得到了VNODE,这就是Vue模板编译的大致流程。

image.png

既然我们不能再template模板中去掉最外层的div,那么我们就从渲染函数render中入手。

递归组件 渲染函数版本

  • LoopComponent.js 文件
export default {
  name: 'LoopComponent',

  props: {
    list: {
      type: Array,
      default: () => {
        return []
      }
    }
  },
  methods: {
    loopH(h, list) {
      return h(
        'div',
        {
          attrs: {
            class: 'list-item'
          },
          props: {
            list: list
          }
        },
        [
          h('div', {
            attrs: {
              class: 'item-name'
            }
          }, [
            h('span', list.name)
          ]),
          (() => {
              if (!list.children || !list.children.length) return []
              return list.children.map((item) => {
                return this.loopH(h, item)
            })
          })()
        ]
      )
    }
  },
  mounted() {
  },
  render(h) {
    return this.loopH(h, this.list[0])
  }
}

我的render方法有点小特殊,额外封装了个递归方法的loopH()返回vnode对象。

  • main.vue 页面
<template>
    <div class="list-detail">
        <loop-component :list="list"></loop-component>
    </div>
</template>
<script>
    export default {
        list: [{
          name: "广东",
          children: [{
              name: "广州",
              children: [{
                  name: "天河区"
                },
                {
                  name: "黄浦区"
                }]
            },{
              name: "深圳",
              children: [{
                  name: "福田区"
                },
                {
                  name: "南山区"
                }]
            }]
        }]
    }
</script>

render递归组件和template递归组件的html结构对比

  • template版本

image.png

  • render版本 image.png

image.png

从上面两个结构html结构图看出,render版本的已经不存在额外的元素,渲染的每个元素都是我们所想要的,这样就很nice,完美。

总结

虽然使用render函数能渲染出更符合开发人员预期的html结构、减少html层级关系,但是其代码写起来并没有template模板那么通俗易懂(当然,写习惯的大佬例外....),两者各有利弊,按实际场景使用,个人感觉只有在template模板实现不了的需求才会去用render函数去实现。