写在前面:
在使用一些开源组件库的时候,我们总能发现其使用了options选项配置 + render函数/slot插槽的写法来展示数据:
如Element的自定义表头渲染:
那么这些是如何实现的呢?,今天我们来探索一下可以实现的方法:
案例描述:实现一个能通过options配置 + render函数写法的组件来展示详情数据列表
1. 在父组件中使用详情列表组件和定义options数据:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<h3>使用命名slot渲染函数或者createElement(h函数渲染)展示详情信息:</h3>
<DetailsList :options="options" :data="details">
<template slot="age" slot-scope="{ scope }">
<span style="color: green;">{{ scope.data.age }}</span>
</template>
</DetailsList>
</div>
</template>
<script>
import DetailsList from './components/DetailsList.vue'
export default {
name: 'App',
components: {
DetailsList
},
data() {
return {
details: {
name: '小明',
age: '18',
tel: '12345678',
phone: '13100000000',
address: '中国'
},
options: [
{
label: '姓名:',
prop: 'name' // 默认展示
},
{
label: '年龄:',
prop: 'age',
slot: 'age' // 自定义插入
},
{
label: '电话:',
prop: 'tel', // 使用slot渲染函数jsx
renderS: (value, data) => {
const address = data.address || '未知';
return (
<div class="tel" onClick={this.handleClick.bind(this, value)}>
<span style="color: red;">{address}</span>
<span> - </span>
<span>{value}</span>
</div>
);
}
},
{
label: '手机:',
prop: 'phone', // 使用h函数渲染
renderH: (h, value, data) => {
// const h = this.$createElement; // 不使用子组件ComponentRender组件的render函数,也可直接获取h函数
const address = data.address || '未知';
return h('div', {
class: 'phone',
on: {
click: () => this.handleClick(value)
},
}, [
h('span', {style: 'color: green;'}, address),
h('span', '-'),
h('span', value),
]);
}
},
]
}
},
methods: {
handleClick(msg) {
alert(msg)
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 20px;
}
.tel,.phone {
width: 100%;
font-size: 18px;
font-weight: 600;
cursor: pointer;
}
</style>
2. 详情列表组件DetailsList.vue的实现:
<!-- 使用slot render 渲染的的列表 -->
<template>
<div class="details-list">
<div v-for="(opt, i) in options" :key="opt.prop">
<div class="item-box">
<div class="item-label">{{opt.label}}</div>
<!-- 自定义插入 -->
<slot v-if="opt.slot" :name="opt.slot" :scope="{ data: data, opt: opt, index: i }"></slot>
<!-- 默认展示 -->
<template v-else>
<!-- slot函数renderS渲染 -->
<template v-if="opt.renderS">
{{ renderToHtml(opt, i) }}
<slot :name="opt.prop" />
</template>
<!-- H函数renderH渲染 -->
<template v-else-if="opt.renderH">
<ComponentRender :opt="opt" :data="data" />
</template>
<!--普通渲染-->
<template v-else>
<div class="item-value">{{data[opt.prop]}}</div>
</template>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DetailsList',
components: {
ComponentRender: {
name: 'ComponentRender',
props: {
opt: Object,
data: Object,
},
render(h) { // 使用组件自带的render方法提供h函数 (createElement)
// const h = this.$createElement; // 也可直接获取h函数
let renderH = this.opt.renderH
if (typeof renderH === 'function') {
return renderH(h, this.data[this.opt.prop], this.data)
}
},
// renderError (h, err) { // 渲染出错的提示组件
// return h('pre', { style: { color: 'red' }}, err.stack)
// }
}
},
props: {
options: {
type: Array,
default: () => ([])
},
data: {
type: Object,
default: () => ({})
}
},
data () {
return {};
},
mounted () {},
methods: {
// 使用js动态新增一个slot实现render函数jsx语法渲染
renderToHtml(opt, index) {
if (typeof opt.renderS === 'function') {
try {
const value = this.data[opt.prop];
this.$slots[opt.prop] = [opt.renderS(value, this.data, index)];
} catch (e) {
console.error(e);
}
}
}
}
};
</script>
<style scoped>
.details-list {
width: 100%;
max-width: 450px;
margin: 0 auto;
}
.item-box {
width: 100%;
padding: 20px;
box-sizing: border-box;
background-color: #ffffff;
text-align: left;
overflow: hidden;
color: #142234;
font-size: 16px;
border-bottom: 1px solid rgba(20,34,52,.15);
}
.item-label {
font-size: 16px;
margin-bottom: 15px;
}
.item-value {
font-size: 16px;
}
</style>
3. 提示:
- 案例项目采用vue-cli4创建,vue版本为2.6+,默认项目的babel插件@vue/cli-plugin-babel已经包含了支持vue jsx语法的依赖,因此我们能直接在render函数中使用jsx语法;
- 如果你的项目版本比较旧,不支持jsx语法,那么就需要安装支持vue jsx语法的babel插件:https://github.com/vuejs/jsx-vue2
4. 总结:
- 代码中使用了两种方式来在options选项中实现render函数,
一种是使用js动态新增一个命名slot实现render函数jsx语法渲染;
另一种是使用vue组件自身的render函数提供的h函数来渲染 (或用 const h = this.$createElement );
Vue 推荐在绝大多数情况下使用模板来创建 HTML。然而在一些场景中,需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它更加灵活和可扩展。
此案例是Vue2的实现方式,Vue3采用第二种方式 + 支持jsx语法的依赖如@vitejs/plugin-vue-jsx也能实现同样效果;
完整的项目代码请访问github项目:Vue-render-demo;
如果代码发现可优化,欢迎提issue改进;