表格表头拖拽后,串行了,原来是“key”

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

前言

Vue中key的作用是什么?这个问题大家应该都不陌生。当然,我也接触过,但是还停留在接触这个阶段,当真正遇到问题的时候,差点栽跟头。

我遇到的问题

近几天,有一个表格表头拖拽的需求,如图所示,右侧自定义列的字段是表格中正展示的字段,右侧自定义列中的字段可拖拽,同时在左侧的表格中同步拖拽后的顺序。自身在实践的过程中发现,右侧拖拽后,左侧表格中的显示有问题,比如标题和创建时间位置互换,但是表格中渲染出来的第一列表头是标题,第二列还是标题,下面对应的内容是对的,那这个问题的原因是什么呢?先来看看我的代码实现吧。

image.png

表格拖拽代码

表格代码:

<div class="table-div">
<elable
  ref="table"
  v-loading="loading"
  :data="data"
  v-bind="tableProps"
  style="width: 100%;"
  :row-class-name="rowClassName"
  @header-dragend="handleDrag"
  @row-dblclick="dblclickRow"
  @sort-change="handleSortChange"
  @selection-change="onSelectionChange"
>
  <template v-if="columns.length > 0">
    <el-table-column
      v-for="(column, index) in columns"
      :key="index
      v-bind="column"
      :min-width="column.minWidth || 140"
      :resizable="true"
      :show-overflow-tooltip="true"
    />
  </template>
  <slot v-else />
</el-table>
</div>
复制代码

自定义列拖拽组件封装代码:

<div class="display-field-wrapper">
    <el-popover
      ref="display-field-popover"
      placement="bottom"
      width="216"
      popper-class="custom-field-popover"
      trigger="click"
    >
      <div class="display-field__popover-panel">
        <div class="screening-box">
          <el-select
            v-if="showGroup"
            v-model="typeValue"
            class="display-field__search"
            placeholder="全部字段类型"
          >
            <el-option
              v-for="item in typeOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
        <div class="display-field__title">
          <span class="select-title">{{ enableLabel }}</span>
          <span class="select-counts">{{ fieldCheckedList.length +'/'+ fieldLen }}</span>
        </div>
        <div class="display-field__checked-list">
          <draggable
            v-model="enableFieldsFilter"
            :draggable="draggableClass"
            @sort="onSort"
          >
            <div
              v-for="item in enableFieldsFilter"
              :key="item.prop"
              class="display-field__check-item"
              :class="{ 'display-field__draggable-item': item.can_cancel }"
            >
              <el-checkbox
                v-model="fieldCheckedList"
                :label="item.label"
                :disabled="!item.can_cancel"
                @change="handleCheck(item, -1)"
              />
            </div>
          </draggable>
        </div>
        <div class="display-field__title">
          <span class="select-title">{{ notEnableLabel }}</span>
          <span class="select-counts">{{ (fieldLen - fieldCheckedList.length) +'/'+ fieldLen }}</span>
        </div>
        <div class="display-field__unchecked-list">
          <div
            v-for="item in notEnableFieldsFilter"
            :key="item.prop"
            class="display-field__check-item"
          >
            <el-checkbox
              v-model="fieldCheckedList"
              :label="item.label"
              :disabled="!item.can_cancel"
              @change="handleCheck(item, 1)"
            />
          </div>
        </div>
      </div>
    </el-popver>
  </div>
复制代码

其实当时我在排查的时候,首先在组件中排查,以为拖拽有问题,但是实际上,内容是对的,只是表头有问题,这才想起之前的一个知识点,说实话,这个点早就知道,但是自己也确实没遇到过类似问题,所以也没在意。一般我在写for循环的时候,都会像上述代码中那样写,key值就写个index,一直没遇到过问题。哈哈。

key的真实作用

key是虚拟DOM对象的标识,当数据发生变化时,vue会根据新数据生成新的虚拟DOM,随后VUe进行新虚拟DOM与旧虚拟DOM的差异比较。比较规则是什么呢?

(1)旧虚拟DOM中找到了与新虚拟DOM相同的key: 若虚拟DOM中内容没变,直接使用之前的真实DOM;若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

(2)旧虚拟DOM中未找到与新虚拟DOM相同的key,则创建新的真实DOM,随后渲染到页面中。

问题解决

那上面我的问题就应该有答案了。我是用index值作为的key,那当我拖拽之后,在进行对比的过程中,发现索引值没变,那干脆内容也不变了。后来我将key值加index的同时,还加上了一个会变化的字段,那就是列的定义字段,这样就能保证在进行diff算法的过程中,会有变化啦。

总结

在日常开发中个,尽量选择每条数据的唯一标识作为key,比如id等,避免出现类似问题;如果不对数据的逆序添加、删除等破坏顺序的操作,仅仅用于列表渲染,其实使用index作为key是没有问题的。

Happy Ending.

Guess you like

Origin juejin.im/post/7035450586417856520