vue模板驱动数据发生变动时候的流程思考

63 阅读4分钟
<!--  xtjbk 下面的key和 v-if="!briefMode" 的key有重复,虽然这两个用v-if控制了只会同时显示一个,但是v-if="!briefMode"里面和下面同key的元素会直接复用下面的元素,
      可以深入了解下vue对加key元素的渲染机制,参考gyroscopeDisplay或者radar组件中的`zrender-container-${timeStamp}` class写法
      参见https://v2.cn.vuejs.org/v2/guide/conditional.html 用key管理可复用的元素,和此处发生的情况一模一样
-->
<el-table-column
  v-if="briefMode"
  :width="cn2attributes[thisGlobalNumType]['width']"
  @第一处
  :key="filedCn2En[globalNumType]"
  :label="thisGlobalNumType"
  sortable="cutsom" :sort-orders="['ascending', 'descending']"
>
  <template slot="header">
    <el-popover
      placement="bottom-start"
      trigger="hover"
      :open-delay="300"
      :close-delay="300"
      transition
    >
      <!--  key发生变化的元素总是被强制替换,而不是重复使用它(vue-api关于key的说明)
            xtjbk:此处的插槽中为什么不会根据globalNumType响应式变化,必须在父组件el-table-column加了:key强制重新渲染才有效果.
            插槽处于当前组件el-table-sudy中,访问的也是组件中的data属性,这是为什么?和插槽嵌套了两层(header和reference)有关系吗?需要查看vue源码深入了解下key元素的作用机制
            参考底盘回放中的写法
      -->
      <div slot="reference" class="dynamicHeading">{{thisGlobalNumType}}<i class="el-icon-sort"></i> </div>
      <el-row><el-button style="margin:5px;width: 90px" type="primary" plain size="mini" @click="changeGlobalNumType(T.车辆编号)">{{T.车辆编号}}</el-button></el-row>
      <el-row><el-button style="margin:5px;width: 90px" type="primary" plain size="mini" @click="changeGlobalNumType(T.网关编号)">{{T.网关编号}}</el-button></el-row>
      <el-row><el-button style="margin:5px;width: 90px" type="primary" plain size="mini" @click="changeGlobalNumType(T.车辆识别号)">{{T.车辆识别号}}</el-button></el-row>
      <el-row><el-button style="margin:5px;width: 90px" type="primary" plain size="mini" @click="changeGlobalNumType(T.车牌号码)">{{T.车牌号码}}</el-button></el-row>
    </el-popover>
  </template>
  <!--  el-table-column的默认插槽<作用域插槽>  -->
  <template v-slot="{row}">
    <div style="border-bottom: 1px solid teal;width: fit-content;color:teal;cursor: pointer">{{getNum(row)}}</div>
  </template>
</el-table-column>
<!-- 网关相关编号列 -->
<template v-if="!briefMode">
  <template v-for="numType in numTypeArray">
    <el-table-column
      @第二处
      :key="numType"
      :prop="numType"
      :label="filedEn2Cn[numType]"
      sortable="cutsom" :sort-orders="['ascending', 'descending']"
      :width="cn2attributes[filedEn2Cn[numType]]['width']"
    >
      <template v-slot="{row}">
        <div style="border-bottom: 1px solid teal;width: fit-content;color:teal;cursor: pointer">{{getNum(row,numType)}}</div>
      </template>
    </el-table-column>
  </template>
</template>

在el-table-sudy组件中,有两个标记处,@第一处和@第二处,其中@第一处的key会被@第二处的所包含,导致的现象就是我们打开了此页面且在简略模式下点击切换了某个编号,再将查询组件切换到普通模式的话,就会呈现如下图所示的效果

image.png

及@第二处的和@第一处同key的元素并没有被正确的显示出来,感觉好像是直接复用了@第一处的元素.

大概的流程:v-if控制的模板最终被编译器生成了render函数,每个单文本组件中的模板其实对应webpack中的一个渲染函数模块,每次v-if条件变化时候的响应式变化的流程其实是render函数重新执行了一边并生成vnode节点,经过debug我们可以看到从简略模式切换到普通模式的时候,render函数的确生成了4个正常的vnode节点.如下图所示

image.png 那为什么页面并没有正确显示出来,用的还是之前的呢,这就要说到vue的key了.以下是vue-api文档关于key的说明:

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。 有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

看来是因为@第一处的key被@第二处包含所以导致了这个问题.

我们在变更v-if条件时候,renderh函数重新执行并生成了新的4个vnode节点,但是vue在这个阶段后要使用diff算法来决定最终要渲染的节点,其中在渲染相同key的那个vnode节点元素时候发现之前的vnode节点树中是存在一个相同key的元素的,这时候就抛弃了新的vnode节点,保留之前的@第一处的vnode节点.等diff算法走完,就会将这些最终要渲染的dom交给浏览器渲染进程去执行了,最终的显示效果就是我们看到的效果.感觉vue的异步队列应该发生在diff算法之前,如果多次设置一个值,不清楚render函数会不会执行多次还是在render执行之前就会被确定为最终得一条变化