起因

实现动态搭建 form 表单,需要从 dsl 中组建元素渲染,我们要知道究竟动态函数究竟在 patch 时候差异多少

// 最小demo
<component :is="renderFunc"></component>
const renderFunc = () => {
  return h('div', {innerHTML: props.a})
}

源码分析

  1. 首先我们先建立两个最小 demo,通过 perfomance 和 debug 断点手段去分析

    <template>
      <div>
        <button @click="handleChange"></button>
        <component :is="HelloWorld" :a="a"></component>
      </div>
    </template>
    
    <script setup> 
    import { ref } from 'vue';
    import HelloWorld from './components/HelloWorld.vue'
    const a = ref('1')
    const handleChange = () => {
      a.value = Math.random().toString()
    }
    </script> 
    

    情况 1, h 函数

    <template>
     <component :is="renderFunc"></component>
    </template>
    
    <script setup> 
    import { h } from 'vue';
    /* eslint-disable */
    const props = defineProps({
      a: String
    })
    const renderFunc = () => {
      return h('div', {innerHTML: props.a})
    } 
    </script>
    

    情况 2, 组件

    <template>
    <ITest :a="props.a"></ITest>
    </template>
    
    <script setup> import Itest from './ITest.vue'
    /* eslint-disable */
    const props = defineProps({
      a: String
    }) </script>
    
    <template>
        <div>{{props.a}}</div>
    </template>
    <script setup> /* eslint-disable */
     // eslint-disable-next-line
    const props = defineProps({
        a: String
    }) </script>
    
  2. 通过点击按钮触发 a 值变化查看渲染流程区别

    情况 1

    情况 1

    情况 2

    情况 2

    我们可以观察到情况 1 在第一次 patch 的时候 renderfunc 这个子组件是没有触发到内部的 patch,而是通过 updateProps 再度调用 renderfunc 进入内部,从而内部 patch 的。

    部分源码路径

    部分源码路径

    这里我们可以观察到 renderfunc 这个组件的 patchflag 为 0,shapeflag 为 2(patch 时这两个 key 启到判断作用),进入 processComponent ⇒ updateComponent,因为新的 component 为空所以直接赋值旧的就结束了。第一次的 patch 就这样结束。又由于 h 里面使用 props 会触发收集所以在 updateprops 的时候就会调用 renderfunc 这个函数,从而触发 patch。

总结

通过源码的 debug 和 perfomance 的记录我们可以得出使用动态渲染这个写法确实会造成一些性能上的损耗,多次多层的不断触发渲染以及 patch 会更加明显的看出来。