这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战
前言
Vue中key的作用是什么?这个问题大家应该都不陌生。当然,我也接触过,但是还停留在接触这个阶段,当真正遇到问题的时候,差点栽跟头。
我遇到的问题
近几天,有一个表格表头拖拽的需求,如图所示,右侧自定义列的字段是表格中正展示的字段,右侧自定义列中的字段可拖拽,同时在左侧的表格中同步拖拽后的顺序。自身在实践的过程中发现,右侧拖拽后,左侧表格中的显示有问题,比如标题和创建时间位置互换,但是表格中渲染出来的第一列表头是标题,第二列还是标题,下面对应的内容是对的,那这个问题的原因是什么呢?先来看看我的代码实现吧。
表格拖拽代码
表格代码:
<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.