Vue删除列表最后一个元素后出现undefined错误求助
问题排查:Vue中使用splice删除列表最后一项时出现undefined错误
问题描述
这是我正在开发的大型组件的简化版本,遇到一个问题:使用splice删除列表最后一项时会出现undefined错误,触发场景如下:
- 逐个删除元素时无错误,仅当删除最后一项且列表中至少存在一项在其前方时才会触发错误;
- 移除v-for中的
@click="handleProductrowTap(index)"后,错误不再出现。
代码示例
<template> <div v-for="(row, index) in this.rows" :key="index" v-bind:id="`row-${index}`" @click="handleProductrowTap(index)"> <div v-bind:id="`row-${index}`" v-bind:class="rowCssDecorations(row, index)"> <span>{{ row.text }}</span> <button class="borderNormal" style="background-color: white; border:black;" @click.prevent="deleteRow(row, index)">Delete me </button> </div> </div> </template> <script> export default { name: 'TestRow', created() {}, data() { return { rows: [ {'id':1, text: 'Granny smith', type: 'apple'}, {'id':2, text: 'Conference', type: 'pear'}, {'id':3, text: 'Alpine', type: 'strawberry'}, ], currentRowIndex: 0 } }, methods: { //returns css styling to every row in v-for. //It sets color of the row according to the row.type and highlights selected row (if currentRowIndex == index). rowCssDecorations(row) { var resultingCss = '' //change color according to rows.type switch (row.type) { case 'apple': resultingCss = resultingCss + 'appleClass' break case 'pear': resultingCss = resultingCss + 'pearClass' break case 'strawberry': resultingCss = resultingCss + 'strawberryClass' break default: resultingCss = resultingCss + 'defaultClass' break } //Hliglight the current row if (this.rows[this.currentRowIndex].id == row.id) { resultingCss = resultingCss + ' borderSelected ' } else { resultingCss = resultingCss + ' borderNormal ' } return resultingCss }, //deletes the row deleteRow(index) { this.rows.splice(index, 1) }, //Simply sets the selected row id to currentRowIndex . handleProductrowTap( index) { this.currentRowIndex = index }, }, } </script> <style lang="scss" scoped> .appleClass { background-color: greenyellow; } .pearClass { background-color: palegoldenrod; } .strawberryClass { background-color: red; } .defaultClass { background-color: gainsboro; } .borderSelected{ border-color: black; border-width: 4px; } .borderNormal{ border-color: black; border-width: 2px; } </style>
错误原因分析
错误的核心是**currentRowIndex保存的旧索引在删除最后一项后超出了数组的有效范围**:
- 点击最后一项时,
handleProductrowTap会把currentRowIndex设为最后一项的索引(比如原数组有3项时,索引为2); - 删除最后一项后,数组长度缩短,有效索引范围变小(比如变为0和1),但
currentRowIndex仍为2; - Vue重新渲染列表时,
rowCssDecorations方法会尝试访问this.rows[this.currentRowIndex].id,此时this.rows[2]为undefined,访问它的id就会抛出错误。
移除v-for的@click后错误消失,是因为此时currentRowIndex始终是初始值0,删除最后一项后数组至少还有一项,this.rows[0]依然存在,不会触发undefined访问。
解决方案
方案一:删除后修正currentRowIndex的取值范围
在deleteRow方法中,删除元素后检查currentRowIndex是否超出当前数组的有效索引,若超出则调整为最后一个有效索引(数组为空时设为0):
deleteRow(index) { this.rows.splice(index, 1); // 调整选中索引到有效范围 if (this.currentRowIndex >= this.rows.length) { this.currentRowIndex = this.rows.length > 0 ? this.rows.length - 1 : 0; } }
方案二:用唯一ID代替索引跟踪选中项
索引会随数组增删变化,改用row的唯一id跟踪选中项更可靠:
- 修改data中的跟踪变量:
data() { return { rows: [ {'id':1, text: 'Granny smith', type: 'apple'}, {'id':2, text: 'Conference', type: 'pear'}, {'id':3, text: 'Alpine', type: 'strawberry'}, ], currentRowId: 1 // 初始选中第一个项的id } }
- 修改点击事件方法和模板调用:
handleProductrowTap(row) { this.currentRowId = row.id; }
<div v-for="(row, index) in rows" :key="index" :id="`row-${index}`" @click="handleProductrowTap(row)">
- 修改样式判断逻辑:
// 高亮当前选中行 if (this.currentRowId == row.id) { resultingCss += ' borderSelected '; } else { resultingCss += ' borderNormal '; }
方案三:增加空值判断避免undefined访问
在rowCssDecorations方法中,先检查目标元素是否存在再访问其属性:
// 高亮当前选中行 const currentRow = this.rows[this.currentRowIndex]; if (currentRow && currentRow.id == row.id) { resultingCss += ' borderSelected '; } else { resultingCss += ' borderNormal '; }
内容的提问来源于stack exchange,提问作者Janis Jakaitis
相关产品推荐
相关产品推荐

