持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
起因
我们项目需要开发一个自定义表单,输入组件和布局都可以自定义,因此使用render函数实现更好的控制渲染结果
我们有一个表单组件,大概是大概是这样的
//mp-form.vue
<script>
export default {
name: "mp-form",
props:{
form_data:Object,
inputs:Array,
},
render(createElement, context) {
let inputs = [];
this.inputs.forEach(input =>{
let [component,options] = input;
if(options['v-if'] && !options['v-if']()){
//如果返回false,不渲染
return;
}
options = Object.assign({},options);
inputs.push(<div class="item">
{options.label}
{createElement(component,options)}
</div>);
});
return (<a-form>{inputs}</a-form>)
}
}
</script>
<style scoped>
.item{
display: flex;
}
</style>
传入一个数组,渲染组件,这个数组是一个json,服务端返回来可以直接用,而不用改代码
//edit.vue
<template>
<mp-form :inputs="inputs" :form_data="form_data"/>
</template>
<script>
import MpForm from "./mp-form";
export default {
name: "edit",
components: {MpForm},
data(){
return {
form_data:{},
is_show:true,
inputs:[
['a-input',{label:'a1',props:{}}],
['a-input',{label:'a2',props:{},'v-if':()=>this.isShowA2()}],
['a-input',{label:'a3',props:{},on:{change:this.changeA3}}],
]
}
},
methods:{
isShowA2(){
return this.is_show;
},
changeA3(e){
if(e.target.value == 'bb'){
this.is_show = false;
}
}
}
}
</script>
<style scoped>
</style>
编辑表单
运行出来是这样的,这里有一个功能就是如果a3输入的值是"bb",就隐藏a2
看起来好像没啥问题,但是却有一个很大的问题
期望结果是这样的
实际上结果是这样的,输入了bb,a2确认隐藏了,a3输入值为空了
实际上原因是a3被销毁,重新渲染了,如果a3是自定义输入框,就可以在beforeDestroy、beforeCreate打印,被销毁和创建
这里最关键的一句话就是,发现false的时候,还需要选渲染一个空节点
//error
if(options['v-if'] && !options['v-if']()){
//如果返回false,不渲染
return;
}
正确的应该这样
//ok
if(options['v-if'] && !options['v-if']()){
//如果返回false,不渲染
inputs.push(createElement('',{}));
return;
}
修改后,效果就正常了
同时看到dom结构,也有一个空节点了
总结
我想原因在于vue的真正渲染时,比较vnode的时候,因为是一个列表,长度不一样了,所以直接重新渲染了后面的节点,突然想到如果加一个key应该也不会导致重新渲染吧?可以下次验证一下
另外就是空节点的应用,想一下弹窗把dom放到body上的实现,应该也是用了空节点,刚开始没渲染显示的时候,也是渲染的一个空节点,等显示的时候再渲染真实的dom,并且移动到body上