是 Vue.js 中处理批量渲染时非常实用的写法,尤其适合需要一次循环生成多个兄弟节点的场景。与此同时,key 属性应该放在哪里、是否必须设置、应该使用什么值,也是开发过程中比较容易混淆的问题。
正确理解 与 key 的关系,不仅可以避免 Vue 的警告,还能让虚拟 DOM 在列表更新时进行更加准确的节点复用。
一、 为什么需要关注 key
普通的 v-for 通常直接作用于一个真实 DOM 元素,例如:
vue
v-for="item in list" :key="item.id"> {{ item.name }}
这里的 key 很直观,它属于 v-for 所生成的 li 节点。
但如果一次循环需要生成多个元素,就不能简单地给这些元素增加一个额外的父标签,否则可能改变最终 DOM 结构。这时候可以使用 :
vuev-for="item in list">{{ item.name }}{{ item.description }}
template 本身不会被渲染成实际 DOM 元素,因此最终页面中仍然只有 div 和 span。
此时就产生了一个关键问题:key 应该放在 上,还是放在内部的某个元素上?
答案取决于使用的 Vue 版本。
二、Vue 2 中 的 key 使用方式
如果项目使用的是 Vue 2,key 通常应该放在实际渲染的子节点上,而不是依赖 本身作为列表节点。
例如:
vuev-for="item in list">:key="item.id"> {{ item.name }}:key="item.id + '-description'"> {{ item.description }}
不过,这种方式存在一个实际问题:同一个数据项对应多个 DOM 节点时,需要确保每个节点的 key 唯一。
更常见的处理方式,是根据具体 DOM 结构重新组织模板。如果多个元素本身应该作为一个整体进行更新,可以考虑增加实际的包裹元素:
vuev-for="item in list" :key="item.id">{{ item.name }}{{ item.description }}
如果业务允许增加一层 DOM,这种写法通常更加简单。
三、Vue 3 中 推荐将 key 放在 template 上
Vue 3 对 的 key 行为进行了调整。
推荐写法如下:
vuev-for="item in list" :key="item.id">{{ item.name }}{{ item.description }}
这里的 key 直接属于 :
vuev-for="item in list" :key="item.id">
这种方式特别适合一个列表项对应多个兄弟节点的情况。
例如:
vuev-for="user in users" :key="user.id"> {{ user.name }} {{ user.email }}
最终 DOM 结构类似:
HTML张三 zhangsan@example.com
李四 lisi@example.com
页面中不会出现 标签,但 Vue 可以通过 user.id 识别这一组节点对应的数据项。
四、为什么 Vue 3 要把 key 放到 template 上
理解这一点,需要先明确 key 的作用。
key 并不是简单用来消除控制台警告的属性,而是 Vue 在更新列表时识别虚拟 DOM 节点的重要依据。
假设原始数据为:
JavaScript[ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' } ]
之后删除第一条数据:
JavaScript[ { id: 2, name: '李四' }, { id: 3, name: '王五' } ]
如果没有正确的 key,Vue 需要根据节点位置判断哪些内容发生了变化。
而使用:
vuev-for="item in list" :key="item.id">
之后,每一组由 template 生成的节点都有明确的数据身份:
id=1 → 第一组节点 id=2 → 第二组节点 id=3 → 第三组节点
删除 id=1 后,Vue 能够更加准确地判断:
id=1 → 删除 id=2 → 保留 id=3 → 保留
对于包含多个兄弟节点的列表来说,这种分组身份尤其重要。
五、正确示例:template 上设置唯一 key
Vue 3 项目中推荐:
vueclass="user-list"> v-for="user in users" :key="user.id"> {{ user.name }} {{ user.email }}
对应的数据:
JavaScriptconst users = [ { id: 101, name: '张三', email: 'zhangsan@example.com' }, { id: 102, name: '李四', email: 'lisi@example.com' } ]
这里 user.id 是非常合适的 key。
因为它通常具备三个特点:
-
在列表中具有唯一性;
-
数据更新后保持稳定;
-
能够明确对应实际业务数据。
六、不要使用数组 index 作为首选 key
下面这种写法虽然可以运行:
vuev-for="(item, index) in list" :key="index">{{ item.name }}{{ item.description }}
但通常不建议把 index 作为动态列表的长期标识。
例如:
JavaScript[ { id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 3, name: 'C' } ]
如果删除第一项:
JavaScript[ { id: 2, name: 'B' }, { id: 3, name: 'C' } ]
此时索引发生了变化:
原来:0=A,1=B,2=C 现在:0=B,1=C
Vue 看到的是相同的索引节点,但节点对应的数据已经发生变化。
对于纯文本展示,这种情况可能不容易发现问题;但是如果列表中存在输入框、复选框、组件内部状态、动画状态等,就可能产生意外的节点复用问题。
因此更推荐:
vuev-for="item in list" :key="item.id">
而不是:
vuev-for="(item, index) in list" :key="index">
如果列表数据本身没有稳定的唯一 ID,应该考虑在数据进入组件时生成唯一标识,而不是直接依赖数组位置。
七、不要使用随机值作为 key
下面的代码看起来似乎能够保证唯一:
vuev-for="item in list" :key="Math.random()">
实际上这是错误的做法。
因为 Math.random() 每次渲染都可能产生不同的值。
例如第一次渲染:
0.123 0.456 0.789
下一次渲染:
0.321 0.654 0.987
Vue 会认为这些节点全部换成了新的节点,从而无法有效利用之前的 DOM。
同样不推荐:
vue:key="Date.now()"
以及其他每次渲染都会变化的值。
key 的核心不是“每次都唯一”,而是在列表更新过程中保持稳定且能够代表数据身份。
八、key 必须保证同一层级中的唯一性
例如:
vuev-for="item in list" :key="item.id">{{ item.name }}{{ item.description }}
如果数据是:
JavaScript[ { id: 1, name: 'A' }, { id: 1, name: 'B' } ]
那么 key 就发生了重复。
正确的数据应该类似:
JavaScript[ { id: 1, name: 'A' }, { id: 2, name: 'B' } ]
如果业务数据没有唯一 ID,可以使用多个稳定字段组合:
vuev-for="item in list" :key="item.type + '-' + item.code">{{ item.name }}{{ item.description }}
前提是 type + code 的组合在当前列表中确实能够唯一标识一项数据。
九、key 不应该随意放到 template 内部
Vue 3 中,如果代码结构是:
vuev-for="item in list">:key="item.id"> {{ item.name }}{{ item.description }}
这并不能很好地表达“这一组节点属于同一个列表项”。
如果一个 item 对应:
div span button
那么这三个节点实际上是一个逻辑单元。
更合理的 Vue 3 写法是:
vuev-for="item in list" :key="item.id">{{ item.name }}{{ item.description }}
此时 key 与 v-for 位于同一个 上,结构和语义更加清晰。
十、条件渲染和 template v-for 一起使用
还经常与 v-if、v-for 配合使用。
例如:
vuev-for="item in list" :key="item.id">v-if="item.visible"> {{ item.name }}v-if="item.showDescription"> {{ item.description }}
这种情况下,key 依然应该标识整个循环项,而不是只绑定到其中某一个实际元素。
如果逻辑比较复杂,也可以提前处理数据:
JavaScriptconst visibleList = computed(() => { return list.value.filter(item => item.visible) })
模板变成:
vuev-for="item in visibleList" :key="item.id">{{ item.name }}{{ item.description }}
这种方式通常更容易维护。
十一、嵌套 template v-for 的 key 如何设置
遇到嵌套循环时,每一层循环都应该使用自己的稳定标识。
例如:
vuev-for="category in categories" :key="category.id"> {{ category.name }} v-for="product in category.products" :key="product.id">{{ product.name }}{{ product.price }}
外层:
vue:key="category.id"
内层:
vue:key="product.id"
两层 key 的作用范围不同,不需要把它们拼成一个全局 ID。
如果内层数据的 id 只在当前分类中唯一,这种写法通常也可以满足当前层级的节点识别需求。
十二、什么时候可以不用 key
并不是所有 v-for 都必须显式指定 key。
例如简单的静态文本列表:
vuev-for="item in items"> {{ item }}
如果列表只是简单展示,并不存在组件状态或复杂 DOM 更新需求,Vue 可以进行默认处理。
但是对于实际项目中的动态列表,尤其是以下情况:
输入框 复选框 表单组件 动态组件 列表排序 列表插入 列表删除 列表过滤 动画过渡 组件内部存在状态
建议明确提供稳定的 key。
对于 而言,如果使用 Vue 3,通常可以直接采用:
vuev-for="item in items" :key="item.id">
这样既符合列表身份管理的设计,也方便后续维护。
十三、常见错误写法总结
错误一:使用 index 作为动态列表 key
vuev-for="(item, index) in items" :key="index">
不是绝对禁止,但对于会发生增删、排序、过滤的列表不推荐。
错误二:使用随机数
vuev-for="item in items" :key="Math.random()">
会导致 key 不稳定。
错误三:使用当前时间
vuev-for="item in items" :key="Date.now()">
同样无法稳定标识列表项。
错误四:使用重复业务字段
vuev-for="item in items" :key="item.name">
如果 name 可能重复,就不适合作为唯一 key。
错误五:Vue 3 中没有正确理解 template 的 key
推荐:
vuev-for="item in items" :key="item.id">{{ item.name }}{{ item.description }}
而不是为了设置 key 强行增加无意义的 DOM 包装层。
十四、Vue 3 项目的推荐写法
如果当前项目使用 Vue 3,可以优先遵循下面这个模板:
vuev-for="item in items" :key="item.id"> {{ item.title }} {{ item.content }}class="divider">