让你的组件更灵活 --- vue2之渲染动态组件

513 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情


前言

日常工作中,经常会遇到很多相似并重复的需求,为了提高工作效率我们通常会将一些组件进行封装,也包括了对一些UI库的二次封装,本文以el-table为例子,介绍一个提高组件代码灵活性的方法,让我们封装的vue组件支持动态渲染。

常规的el-table

我们以el-table为例子,这是一个常规的表格

<el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="日期" width="180">
    </el-table-column>
    <el-table-column prop="name" label="姓名" width="180">
    </el-table-column>
    <el-table-column prop="address" label="地址">
    </el-table-column>
</el-table>
export default { 
    data() { 
        return { 
            tableData: [
                { date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, 
                { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, 
                { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, 
                { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }
            ]
        } 
    }
}

发现重复的问题

我们观察template可以发现,el-table-column这里写了三份,显然是不灵活的,我们可以绑定给一个列表对象,在页面中进行一个遍历

<template v-for="(item, index) of list">
    <el-table-column :prop="item.prop" :label="item.label" :width="item.width"> 
    </el-table-column>
</template>
    data() {
        return {
            list: [
                {prop: 'date', label: '日期', width: '200'},
                {prop: 'name', label: '姓名', width: '200'},
                {prop: 'address', label: '地址', width: '200'}
            ]
        }
    }

在这里的list与上面写了三次el-table-column是一样的效果,我们可以把这个list拿到父组件,传参使用。

动态列内容

我们有个需求,想让表格的某一列渲染成我们想要的值,我们可以使用el-table组件的formatter来格式化,也可以使用v-html进行改造

v-html改造

<template v-for="(item, index) of list">
    <el-table-column :prop="item.prop" :label="item.label" :width="item.width">
        <template slot-scope="scope">
            <span v-html="item.asyncHtml ? item.asyncHtml(scope.row[item[prop]], scope) : scope.row[item[prop]]"></span>
        </template>
    </el-table-column>
</template>
    data() {
        return {
            list: [
                {prop: 'date', label: '日期', width: '200'asyncHtml: (scope) => `<span>{{scope}}</span>`
                },
                {prop: 'name', label: '姓名', width: '200'},
                {prop: 'address', label: '地址', width: '200'}
            ]
        }
    }

动态组件

  • 由于v-html只是将内容按普通 HTML 插入,会有XSS攻击风险,而且最重要的是不支持传一个组件过去。
  • 如果我们想在列中,显示一个组件,这里肯定不适合写死在子组件中,我们可以使用render进行改造
<template slot-scope="scope">
    <render-dom
      v-if="item.render && typeof item.render === 'function'"
      :scopeRow="scope.row"
      :item="item"
      :index="index"
      :render="item.render"
      :value="scope.row[item[prop]]"
    />
    <span v-html="item.asyncHtml ? item.asyncHtml(scope.row[item[prop]], scope) : scope.row[item[prop]]"></span>
</template>

这里我们创建了组件render-dom,在这个组件中我们使用返回了render方法,并调用了item.render,且将组件的h(createElement)与组件接受的其他属性this.$attrs传给了最外层父组件的item对象,代码如下

<script functional>
export default {
  name: 'RenderDom',
  props: {
    render: {
      type: Function,
      render: () => {}
    }
  },
  render(h) {
    // 这里的h和this.$attrs是我们要传给list的item对象的参数,很关键
    return this.render(h, this.$attrs)
  }
}
</script>

比如我们需要新增一个评分组件的列,那么list列表对象如下:

list = [
    // asyncHtml
    {prop: 'date', label: '日期', width: '200'asyncHtml: (scope) => `<span>{{scope}}</span>`
    },
    // 普通渲染
    {prop: 'name', label: '姓名', width: '200'},
    {prop: 'address', label: '地址', width: '200'},
    // 更灵活的动态组件
    {prop: 'rate', label: '评分',
      render: (h, { value }) => {
        return h('span', [
          h('el-rate', {
            attrs: {
              value,
              disabled: true
            }
          }),
          h('span', `${value}分`)
        ])
      },
    },
]

实际应用

20220825-001624.jpeg

结语

  • 动态组件的核心就是render,这也是字符串模板的代替方案,可以发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。因为是虚拟DOM,所以也支持我们传各种组件,更加的灵活。
  • 可以根据实际需求把我们组件中list中对象的render属性封装成纯函数进行使用。