Vue为何复用元素?如何避免复用导致的HTML元素状态残留
Vue列表更新时元素复用导致自定义属性残留的问题
我在Vue项目开发中遇到一个异常行为:更新JavaScript数组后,新的列表项会复用旧的HTML元素,导致旧元素上的自定义属性被新项继承,出现不符合预期的状态。
示例代码
HTML部分
<head> <title>Test</title> <meta charset="UTF-8"> <script src="https://unpkg.com/vue@3" defer></script> <script src="script.js" defer></script> <style> div[time-isselected="true"] { background: rgb(0, 255, 0); } </style> </head> <body> <div class="day container"> <div class="selection" v-for="day in daysList"> <input type="radio" class="radio-day" name="radio" :id="returnTheInput(day)" :value="returnTheInput(day)" @click="setSelectedDay(day)"> <label :for="returnTheInput(day)">{{day}}</label> </div> </div> <div class="hour-container"> <div v-for="hour in hoursList" class="hour" :id="returnTheInput(hour)" @click="setSelectedHour(hour)"> {{hour}} </div> </div> </body>
JavaScript部分
let daysList = ["mon 15","tue 16"]; let hoursList = []; let selectedDay = undefined; const valuesForTest = { [daysList[0]]: ["10:00", "11:00"], [daysList[1]]: ["15:00", "16:00"] } const { createApp } = Vue; const vm = Vue.createApp({ data(){ return { daysList: daysList, hoursList: hoursList }; }, methods: { returnTheInput(input){ return input; }, setSelectedDay(day){ selectedDay = day; vm.hoursList.splice(0, hoursList.length); //Vue is reactive to splice for(let i = 0; i < valuesForTest[selectedDay].length; i++){ vm.hoursList.push(valuesForTest[selectedDay][i]); } }, setSelectedHour(hour){ document.getElementById(hour).setAttribute("time-isselected", "true"); } } }).mount("body");
复现步骤
- 选择某一天
- 选择一个小时(点击它)
- 选择另一天
执行上述操作后,新切换的小时项会显示为选中状态,这并非预期结果。原本以为新项会对应全新的HTML元素,但实际上Vue在复用旧元素。
解决方法
方法1:给v-for添加唯一key属性
Vue的v-for默认会复用元素提升性能,它基于元素顺序而非内容判断是否复用。给v-for绑定唯一key可以让Vue识别每个项的唯一性,从而避免复用旧元素:
修改小时列表的v-for代码:
<div v-for="hour in hoursList" :key="hour" class="hour" :id="returnTheInput(hour)" @click="setSelectedHour(hour)"> {{hour}} </div>
这里用hour作为key(每个小时值唯一),添加后Vue会在列表更新时销毁旧元素并创建新元素,旧元素的自定义属性就不会被继承。
方法2:用Vue响应式状态管理选中状态(更推荐)
直接操作DOM设置属性不符合Vue响应式理念,更好的做法是将选中状态存入响应式数据,通过数据驱动视图:
- 在
data中添加选中状态:
data(){ return { daysList: daysList, hoursList: hoursList, selectedHour: null }; }
- 修改
setSelectedHour方法,更新响应式状态而非直接操作DOM:
setSelectedHour(hour){ this.selectedHour = hour; }
- 修改小时元素模板,通过状态绑定属性:
<div v-for="hour in hoursList" :key="hour" class="hour" :id="returnTheInput(hour)" @click="setSelectedHour(hour)" :time-isselected="selectedHour === hour"> {{hour}} </div>
切换日期时,selectedHour不会自动关联新的小时项,视图会根据状态自动更新,完全避免DOM操作带来的问题。
内容的提问来源于stack exchange,提问作者Francesco Urdih
相关产品推荐
相关产品推荐

