携手创作,共同成长!这是我参与「掘金日新计划 · 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}分`)
])
},
},
]
实际应用
结语
- 动态组件的核心就是render,这也是字符串模板的代替方案,可以发挥 JavaScript 最大的编程能力。该渲染函数接收一个
createElement方法作为第一个参数用来创建VNode。因为是虚拟DOM,所以也支持我们传各种组件,更加的灵活。 - 可以根据实际需求把我们组件中
list中对象的render属性封装成纯函数进行使用。