如果你写过vue render函数,可能需要注意一下

832 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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>

编辑表单

image.png

运行出来是这样的,这里有一个功能就是如果a3输入的值是"bb",就隐藏a2

看起来好像没啥问题,但是却有一个很大的问题

期望结果是这样的

image.png

实际上结果是这样的,输入了bb,a2确认隐藏了,a3输入值为空了

c3ad1d8e-dcd7-4239-907f-32a0bc184381.gif

实际上原因是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;
      }

修改后,效果就正常了

274cf7ad-e10f-4dbc-823a-084dadb84566.gif

同时看到dom结构,也有一个空节点了

image.png

总结

我想原因在于vue的真正渲染时,比较vnode的时候,因为是一个列表,长度不一样了,所以直接重新渲染了后面的节点,突然想到如果加一个key应该也不会导致重新渲染吧?可以下次验证一下

另外就是空节点的应用,想一下弹窗把dom放到body上的实现,应该也是用了空节点,刚开始没渲染显示的时候,也是渲染的一个空节点,等显示的时候再渲染真实的dom,并且移动到body上