最近在开发一个商品管理系统时遇到了一个奇怪的问题:在使用 uniapp 开发的页面中,有一个商品列表,每个商品都有数量输入框,但是在输入框中输入数字时,每输入一个字符,输入框就会自动失去焦点,用户体验非常糟糕。
问题场景
具体场景是这样的:用户扫描商品二维码后,会在 productList 数组中添加一个商品对象,每个商品对象都包含商品信息和数量字段,数量字段与 input 输入框进行双向绑定。用户需要在输入框中修改商品数量,但每次输入都会导致输入框失焦。
错误代码
1 | <view v-for="(item, i) in productList" :key="item"> |
1 | updateQuantity(e, i) { |
问题原因
问题的根本原因在于 v-for 中的 :key="item" 设置不当。当 input 数据发生变化并触发模型更新后,Vue 需要重新渲染列表项。由于 key 值绑定的是整个 item 对象,而 item 对象中的 quantity 属性发生了变化,导致 Vue 认为这是一个新的列表项,从而重新创建了整个 view 组件,包括其中的 input 元素。
在 Vue 的 diff 算法中,key 是用来标识列表项唯一性的重要属性。如果 key 值发生变化,Vue 会认为这是一个全新的元素,而不是对原有元素的更新,因此会销毁旧元素并创建新元素,这就导致了 input 失去焦点。
解决方案
解决方法很简单,将 key 值改为数组索引或者对象中不会变化的唯一标识:
1 | <view v-for="(item, i) in productList" :key="'product-' + i"> |
或者如果商品对象有唯一的 ID 字段,使用 ID 作为 key 值会更好:
1 | <view v-for="(item, i) in productList" :key="item.id"> |
总结
在使用 v-for 循环渲染列表时,key 的选择非常重要:
- 不要使用会变化的对象作为 key:如果对象的属性会发生变化,Vue 会认为这是不同的元素
- 优先使用唯一且稳定的标识:如数据库 ID、唯一编码等
- 谨慎使用数组索引:只有在列表不会发生增删改操作时才推荐使用索引作为 key
- 避免使用 random 或时间戳:这会导致每次渲染都重新创建元素
这个问题在 Vue 开发中比较常见,特别是在处理表单列表时。正确理解 Vue 的 key 机制,能够避免很多类似的渲染问题.