02|Diff 与 key
第七章 / 02。目标:把 key 当作同层兄弟节点的身份,而不是消除警告的装饰。
Diff 比较的是哪一层
Vue patch 一个父节点的 children 时,处理的是这组同层兄弟。新旧节点类型和 key 都相同,才可视作同一个身份继续 patch。对于 keyed children,典型过程是:
- 从头同步仍相同的节点;再从尾同步。
- 若一边耗尽,剩余新节点直接挂载,或剩余旧节点直接卸载。
- 中间乱序区建立 key 到新下标的映射,找到哪些旧节点仍存在。
- patch 可复用节点、卸载不存在的节点;需要时根据最长递增子序列减少 DOM 移动。
“减少移动”是实现策略,正确性先来自身份匹配。key 只在当前兄弟集合中要求唯一,不是全应用主键。
可重放实验:输入值跟着谁走
把下面组件放进 Vite 项目的 App.vue:
<script setup lang="ts">
import { nextTick, ref } from 'vue'
type Row = { id: string; label: string }
const rows = ref<Row[]>([
{ id: 'a', label: 'Alpha' },
{ id: 'b', label: 'Beta' }
])
async function prepend() {
rows.value.unshift({ id: 'x', label: 'Xray' })
await nextTick()
console.table([...document.querySelectorAll('li')].map(li => ({
id: li.dataset.id,
input: li.querySelector('input')?.value
})))
}
</script>
<template>
<button @click="prepend">头部插入</button>
<ul>
<li v-for="row in rows" :key="row.id" :data-id="row.id">
{{ row.label }} <input :aria-label="`${row.label} 备注`">
</li>
</ul>
</template>先在 Beta 的输入框键入 B-note,再点击按钮。使用 row.id 时,输入值仍属于 b 对应的 DOM。把循环改为 v-for="(row, index) in rows"、key 改为 :key="index",刷新恢复初始数据后重试,浏览器保留的是“原位置”的输入状态,标签与输入可能串台。受控输入若每次都由 :value 覆盖,表面问题可能消失,但组件实例状态、焦点或第三方 DOM 状态仍可能暴露错误身份。
乱序区怎样减少移动
看旧顺序 a,b,c,d 与新顺序 b,d,a,c。先按新顺序写出每项在旧数组的位置,得到 [1,3,0,2]。递增子序列可以是 [1,3](b,d)或 [0,2](a,c):这些节点的相对顺序本来正确,保留其中最长的一组,移动另外两项即可。递增不要求连续,所以不能只找最长连续片段。
为什么从后向前放?假设选择保留 b,d,先把 c 移到末尾,再把 a 插到 c 前;此时 d 的后继 a 已经就位,b/d 无须移动,最终是 b,d,a,c。渲染器可以把已经定位的右邻节点当 anchor。新增 key 没有旧下标,要挂载;消失 key 要卸载,它们不能加入“可保留旧身份”的序列。最长递增子序列只优化移动数,不能修复错误 key。
用四个真实输入框验证这个变体:重排前保存每个元素引用,重排后对所有 id 检查引用和值都保持。若只比最终文字,无法证明旧实例确实复用了。
key 的三个常见误用
| 写法 | 变化时发生什么 | 适用判断 |
|---|---|---|
:key="index" | 插入/排序后同一位置被当成同一身份 | 仅静态、不会重排且无局部状态列表才可能安全 |
:key="Math.random()" | 每次 render 都是新身份,整行重建 | 几乎总是错误,也破坏焦点与缓存 |
:key="row.id" | 业务对象跨重排维持身份 | id 必须稳定且在兄弟中唯一 |
key 也可用于故意重置。比如 <Editor :key="documentId" /> 在文档切换时重建编辑器,使旧草稿状态不跨文档复用。此时“丢状态”正是合同,应在测试中写明。
练习:判断重排后的身份
创建三行 { id: 'a' | 'b' | 'c', label },每行包含非受控输入框。给 b 输入 draft 后,把顺序改为 c,a,b。分别使用 id 与 index 作 key,记录输入框 DOM 是否仍对应 b。提交内容必须包含:两种 key 的观测表、为什么会不同,以及“何时故意改 key 重置组件”的一个例子。
自动化复核需要完成 第九章测试环境 后回访;首次学习按两次独立挂载记录即可。
参考答案:第 7 章第 02 题。