题记
今天从后端传来一个需求,需要在表格中渲染一个select组件,点击切换时调接口修改数据,大概就是这样子。
就以这个例子来看一下vue的render函数吧!
深入了解vue组件
我们都知道vue的单文件组件,就是template、script、style,写在一个.vue结尾的文件中,然后通过打包编译,变成js,但其实,vue也有类似与react的组件写法,那就叫functional组件。
functional组件其实有2种写法,只有一个template或一个对象.
functional template
这个其实在文档中没有展现,在vue-loader的文档中有写.链接
<template functional>
<div>{{ props.foo }}</div>
</template>
当你使用这一种写法时,vue文件中无需写script,这种适用于只有简单样式,简单的dom的受控组件,或者仅仅是用来展示的组件.很多方法需要通过listenear和parent、props去获取.
functional Component
这次我是用的第二种,一个对象表示的组件。它的基本结构如下。
export default {
name: 'xxx', //必须有
functional:true,
props:{}, // 非必须
render:function(h,ctx){
return h('div')
}
}
这里的h其实就是真正的render函数。下面我就以我的需求为例,讲一下这个render函数的使用。
初探render
h其实就是vue的createElement,在文档这样写
createElement( 'div', //参数1
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
{},//参数2
// {Object} // 一个与模板中 attribute 对应的数据对象。可选。
[] //参数3
// {String | Array}
其中:
参数1是你要渲染的dom,这里可以是一个标签,例如div,也可以是一个vnode,或者应该render函数
参数2,是这个dom的一些属性,常用的大概有这些
{
class:{ a:true,b:false}, // class名
style: {color:'red'}, // style
attrs:{} // 标签上的一些属性,例如id,src
domProps:{innerHTML:'xxx'},// dom原生属性
props:{} // 这个就不用多说了
on:{click(){}}, //事件,
nativeOn:{} // 原生dom事件以及$emit
directives:[] // 指令,
scopedSlots
slot
key
ref
refInFor
}
参数3则是需要渲染children
解决这个需求
我们都知道,el-select应该这样使用,因此我们也要这样去渲染相应dom.
<template>
<el-select v-model="value" placeholder="请选择">
<el-option value="1" label="待处理" />
<el-option value="2" label="已处理" />
</el-select>
</template>
渲染第一层dom
首先,我们先渲染一个el-select,并通过props传入一个value。当然,我这个项目是后台管理,已经全局引入了elementUI,因此这里可以直接使用标签渲染.
function(h,ctx){
return h('el-select',{props:{value:'a'}})
}
这是页面就会显示一个select组件,里面是你传入的值,这时候需要渲染children,需要注意的是,每个children本质上还有一个render函数。从原理上讲,render函数就是递归去渲染dom,一直到没有children为止。
渲染第二层dom
因为我这里有2个选项,因此children是一个数组.这里其实可以单独写,也可以使用循环,就相当于v-for。我这里直接使用了map方法.
function(h,ctx){
const list = [{ value: 1, label: '待处理' }, { value: 2, label: '已处理' }]
const options = list.map(e=>h('el-option', { props: { value, label }}))
return h('el-select',{props:{value:'a'}},options)
}
传入value
但是这个是渲染到表格中的,因此需要传入表格中的数据。
function(h,ctx){
const { props: { row:{status} }} = ctx
const list = [{ value: 1, label: '待处理' }, { value: 2, label: '已处理' }]
const options = list.map(e=>h('el-option', { props: { value, label }}))
return h('el-select',{props:{value:status}},options)}
添加事件
function(h,ctx){
const { props: { row:{status} }} = ctx
const list = [{ value: 1, label: '待处理' }, { value: 2, label: '已处理' }]
const options = list.map(e=>h('el-option', { props: { value, label }}))
return h('el-select',{
props:{value:status},
on:{
change:(currentValue)=>{}
}
},options)
}
但是怎么样让他触发父组件的事件,或者给父组件传值呢?我选择的方法是把函数当作proprs传入进去
function(h,ctx){
const { props: { emitChange,row:{status} }} = ctx
const list = [{ value: 1, label: '待处理' }, { value: 2, label: '已处理' }]
const options = list.map(e=>h('el-option', { props: { value, label }}))
return h('el-select',{
props:{value:status},
on:{
change:(currentValue)=>{emitChange(currentValue)}
}
},options)
}
界面响应问题
这时候其实已经能完成大部分功能了,但是这里会出现一个问题,props其实是单向数据流,也就意味着props并不会响应数据变化,导致当选中变化的时候,界面显示并不会改变。
为了解决这个问题,我采用了一个比较投机取巧的方法。props中我不再传入status,而是row.status,由于row是一个引用类型,当我改变row.status就可以被响应。由于emitChange是一个异步方法,因此我把改变status放到这个方法执行成功之后.
function(h,ctx){
const { props: { emitChange,row }} = ctx
const list = [{ value: 1, label: '待处理' }, { value: 2, label: '已处理' }]
const options = list.map(e=>h('el-option', { props: { value, label }}))
return h('el-select',{
props:{value:row.status},
on:{
change:(currentValue)=>{
return emitChange(currentValue)
.then(()=>{
row.status = currentValue
})
}
}
},options)
}
完整代码
const StatusSelect = {
name: 'StatusSelect',
functional: true,
render(h, ctx) {
const { props: { emitChange, row }} = ctx
const list = [{ value: 1, label: '待处理' }, { value: 2, label: '已处理' }]
const options = list.map(e=>h('el-option', { props: { value, label }}))
return h('el-select',
{
props: { value: row.status },
on: {
change: (currentValue) => {
// row.status = currentValue
return emitChange(currentValue, row)
.then(() => { row.status = currentValue })
}
}
},
options)
)
}
}
小技巧
真正使用的时候,其实我们可以不用import引入,直接将这个对象写到components里,这样会更加优化,减少一次编译。
总结
其实vue2的render函数还是一个很有用的技巧,从性能上讲functional组件减少编译,性能更好。当然,我对render函数的使用也不多,所以这篇文章才叫初探。听说vue3的render函数会更加灵活,更加接近底层,后续我也会继续研究。