Skip to content

02|Diff 与 key ​

第七章 / 02。目标:把 key 当作同层兄弟节点的身份,而不是消除警告的装饰。

Diff 比较的是哪一层 ​

Vue patch 一个父节点的 children 时,处理的是这组同层兄弟。新旧节点类型和 key 都相同,才可视作同一个身份继续 patch。对于 keyed children,典型过程是:

  1. 从头同步仍相同的节点;再从尾同步。
  2. 若一边耗尽,剩余新节点直接挂载,或剩余旧节点直接卸载。
  3. 中间乱序区建立 key 到新下标的映射,找到哪些旧节点仍存在。
  4. patch 可复用节点、卸载不存在的节点;需要时根据最长递增子序列减少 DOM 移动。

“减少移动”是实现策略,正确性先来自身份匹配。key 只在当前兄弟集合中要求唯一,不是全应用主键。

可重放实验:输入值跟着谁走 ​

把下面组件放进 Vite 项目的 App.vue:

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 题。

用心记录每一步成长