DOM元素上的key和子组件的key到底是怎样影响组件的渲染的?

0 阅读4分钟

vue3对组件、子组件、DOM元素的渲染

我们先思考一下这两个问题:

  • 在menuOptions发生改变的情况下,a-layout组件及其内部的hrp-g组件会不会重新渲染?
  • 在去掉外层 上的key的情况下,内层的getPageKey()发生改变的情况下,内部的hrp-g会不会重新渲染?
<a-layout class="layout" v-if="layoutType === layoutTypeMap.vertical" style="height: 100%;" :key="new Date().getTime()">
  <a-layout-content style="padding: 20px 50px">
    <a-breadcrumb style="padding-bottom: 20px"  v-if="config.showBreadcrumb">
      <a-breadcrumb-item>Home</a-breadcrumb-item>
      <a-breadcrumb-item>List</a-breadcrumb-item>
      <a-breadcrumb-item>App</a-breadcrumb-item>
    </a-breadcrumb>
    <div class="hrp-layout-content-vertical" :key="getPageKey()">
      <hrp-g
          v-if="init"
          :key="contents.pageId"
          :settings="{
            config:contents,
            data:config.name ? data[config.name] : data,
            topData: topData,
            parentData:data,
            parentConfig:config
        }"
      />
    </div>
  </a-layout-content>
</a-layout>

<div 
  class="hrp-context-menu"
  v-show="menuOptions.length > 0 && menuVisible"
  ref="contextMenuWrapper"
  :style="{
    top: menuTop + 'px',
    left: menuLeft + 'px',
    zIndex: zIndex
  }">
  <hrp-g v-for="(item,index) in menuOptions" :key="index" :settings="{
    config:item,
    data:data,
    topData:topData,
    parentData:parentData,
    parentConfig:parentConfig
  }" />
</div>

上面两个问题的答案是:

  • 问题1:会重新渲染。
  • 问题2:不会重新渲染。

为什么会是这样的结果呢?下面我们分析一下。

首先上面绑定了一个值时间戳的key属性,在Vue中,当你给一个组件添加:key="new Date().getTime()" 时,每次重新渲染时都会生成一个新的时间戳值,这会导致Vue认为这是一个全新的组件实例,从而触发整个组件树的重新创建,包括其所有子组件。基于这个,我们很容易就可以分析得到以下结论:

  • 当 menuOptions 发生变化时,组件会重新渲染
  • 如果 有一个动态变化的key (如时间戳),Vue会认为这是一个新组件
  • 这会导致整个 及其所有子组件(包括 hrp-g )被销毁并重新创建
  • 而当没有这个动态key时,Vue只会更新变化的部分,不会重新创建整个组件树

那么有人可能会问,根据上面的解释,问题2应该是会重新渲染才对,为什么是答案是不会渲染呢。

原因是因为: 1、当去掉外层的动态key之后,当menuOptions变化是,Vue会尝试只更新变化的部分,此时内层的:key="getPageKey()"只会影响

这个DOM元素本身,而不会强制其子组件 hrp-g 完全重建。

2、没有外层动态key时, Vue会尽量复用组件实例,hrp-g组件实例会被保留,只要其props变化时或者它自身的key发生变化(:key="contents.pageId")以及条件渲染的条件发生变化时才会更新。

按照上面的解释,似乎还存在一个问题,什么问题呢?

那就是a-layout上面的动态key应该只影响该组件本身才对,而不应该导致内部的hrp-g组件重新渲染,但实际情况却不是这样,这是为什么呢? 这里存在一个关键区别:

  • Vue对DOM元素和组件的不同处理方式。 当一个普通的DOM元素(如div)的key发生变化时,Vue只会重新创建该DOM元素,但会尝试保留其子组件的状态 当一个组件(如)的key发生变化时,Vue会将其视为一个全选的组件实例,导致整个组件树销毁并重建
  • 组件实例的生命周期 组件是有自己的生命周期和内部状态的,它是一个独立的功能单元。相反,DOM元素没有生命周期和内部状态。 当组件因key变化被视为新组件时,会触发完整的销毁和重建过程,包括其所有子组件。

那么内层的 :key="getPageKey()" 只会影响

这个DOM元素本身,而不会强制其子组件 hrp-g 完全重建。这是怎么做到的呢?

这个主要是Vue的渲染机制决定的。

Vue的渲染过程分为几个关键步骤: 1、虚拟DOM的创建:Vue先创建虚拟DOM树 2、差异比较(Diff算法):比较新旧虚拟DOM树的差异 3、DOM更新:只更新变化的部分,为了提高性能,Vue在更新DOM时会尽可能地保留和复用组件实例。

因为本文重点不在解析diff算法就不展开详细讲diff算法了,后续有需求可以针对Vue3的diff算法出一篇分析文章。