必须为 v-for 添加唯一且稳定的 :key,如 :key="item.id";否则 vue 2 的 diff 算法会错误复用 dom,导致消息气泡错位、箭头反向、事件绑定错乱,小程序平台还会直接报错。

uni-app 的 v-for 不加 key 会直接错乱
不加 :key,消息气泡左右对齐错位、三角箭头方向反了、点击事件绑到错误条目上——尤其在快速收发多条消息时,这种错乱肉眼可见。这不是“偶尔出问题”,而是 Vue 的 diff 算法在 uni-app 中失效的必然结果。
根本原因在于:uni-app(基于 Vue 2)用 key 标识每个节点的身份。没 key 时,Vue 只能按顺序复用 DOM 元素;一旦列表插入/删除中间项,后续所有元素的 DOM 就会被错误复用,数据和视图彻底脱钩。
- 比如用
:key="index":用户在开头unshift新消息,原第 0 条变成第 1 条,但它的 DOM 被当成新消息渲染,导致内容、样式、事件全错配 - 后端返回的
msg.id是稳定标识,:key="item.id"才能让 Vue 准确追踪每条消息的生命周期 - 临时用
:key="item.timestamp + '-' + i"可行,但必须加连字符防拼接冲突(如162000000012345和16200000001+2345可能撞)
小程序平台会直接报错,不是警告
H5 端容忍 :key="item.id + '_msg'" 这类表达式,但微信/支付宝等小程序编译时会直接中断并报错:Non-H5 platform does not support expression in :key。这是因为小程序底层要求 key 是静态可比值,运行时计算会破坏其 diff 性能模型。
常见翻车写法:
-
:key="chart + '_item'"→ 报错,chart是变量,+ 是运算符 -
:key="Math.random()"→ 每次渲染都新建节点,列表滚动卡顿 -
:key="Date.now()"→ 同上,且完全失去复用意义
正确做法只有一条:确保 :key 绑定的是对象字段(如 item.id)或简单字符串字面量(如 'static-key')。
key 写错比不写还危险
看似加了 :key,但若值不唯一或不稳定,问题更隐蔽。例如用时间戳毫秒值作 key,两条消息恰好同毫秒就冲突;用后台分页序号(如 page=2&index=3)则跨页重复。
判断 key 是否合格,就看它是否满足两个条件:
- 同一列表中绝对不重复(哪怕消息重发、缓存回填也要保证)
- 该消息实例生命周期内恒定不变(不能随状态更新而变)
如果后端确实不提供 id,宁可用 :key="item.timestamp + '-' + item.content.slice(0,8)" 做弱唯一,也别碰 index 或随机数。
滚动到底部失效往往也是 key 惹的祸
很多开发者发现调用 this.$nextTick(() => { uni.createSelectorQuery().in(this).select('.msg-list').boundingClientRect(...).exec() }) 拿不到正确高度,根源常是 key 错误导致 DOM 节点被错误复用或未完整挂载。
因为 $nextTick 只保证 Vue 数据已更新,不保证小程序底层 DOM 已完成真实插入。当 key 失效,v-for 渲染出的节点可能被 Vue 认为“可复用”而跳过创建,query 查询自然查无此节点。
所以调试滚动问题前,先检查控制台是否出现 Failed to execute 'querySelector' on 'Document' 类错误,再顺藤摸到 :key 是否真正唯一且稳定。











