Vue 虚拟 DOM Diff 算法与 key 机制原理

一、为什么需要虚拟 DOM 与 Diff

直接操作真实 DOM 成本高(重排/重绘、性能差)。Vue 用 虚拟 DOM(VNode) 对真实 DOM 做轻量 JS 描述,例如:

js 复制代码
{ tag: 'input', props: { disabled: true } }

当数据变化时,Vue 生成新 VNode ,与旧 VNodeDiff(对比) ,算出最小变更集再应用到真实 DOM,避免全量重渲染。这个对比过程就是 Diff 算法

二、Diff 的两类场景

1. 普通元素(非列表)

同层两个 VNode 的对比流程:

  1. 判断是否同一节点 :先比 key,无 key 则比标签名/组件名;不同则销毁旧节点、创建新节点。
  2. 同一节点则增量更新 :仅对比并更新 props、样式、事件等差异项(如 disabled 变化)。
  3. 递归处理子节点:容器节点继续向下 Diff。

2. 列表元素(key 的关键作用)

以列表从 [a, d, e] 变为 [a, b, c, d, e] 为例:

  • 无 key:Vue 就地复用------原 d 的 DOM 被改成 b,原 e 的 DOM 被改成 c,再新建 d/e。
  • 有 key:Vue 通过 key 映射精准定位,在 a 与 d 之间插入 b/c,d/e 的 DOM 保持不变。

三、key 的官方语义

key 主要供 Vue 虚拟 DOM 算法在对比新旧节点时识别 VNode 身份。不使用 key 时,Vue 会尽可能就地修改/复用同类型元素;使用 key 时,则依据 key 变化重排、插入或移除节点。

约束:同一父节点下的子元素 key 必须唯一,重复 key 会导致渲染异常。

四、key 机制同样适用于 v-if

一个常见误区是"key 只对 v-for 有用"。实际上,v-if 在兄弟节点中间位置动态插入新节点时,这些节点同样作为同级子节点参与 Diff。

el-descriptions 内的 el-descriptions-item 为例,抽象为五个节点:

节点 初始状态 属性
a 显示 可编辑
b / c 隐藏(v-if=false 可编辑
d / e 显示 disabled

当 a 取值变化、触发 b/c 展示时,未加 key 的情况下 Vue 可能复用 d/e 的 DOM 来渲染 b/c------这就是就地复用机制在 v-if 场景下的体现

五、如果不遵循 key 规范,会发生什么

以动态表单为例:节点 a 始终显示且可编辑;节点 b、c 由 v-if 控制动态展示,本应可编辑;下方节点 d、e 始终展示且 disabled

未加 key 的写法(有问题的写法):

vue 复制代码
<el-descriptions>
    <el-descriptions-item label="节点 a">
        <el-input v-model="row.a" style="width: 100%" />
    </el-descriptions-item>
    <el-descriptions-item label="节点 b" v-if="showFieldB">
        <el-input v-model="row.b" style="width: 100%" />
    </el-descriptions-item>
    <el-descriptions-item label="节点 c" v-if="showFieldC">
        <el-input v-model="row.c" style="width: 100%" />
    </el-descriptions-item>
    <el-descriptions-item label="节点 d">
        <el-input v-model="row.d" style="width: 100%" disabled />
    </el-descriptions-item>
    <el-descriptions-item label="节点 e">
        <el-input v-model="row.e" style="width: 100%" disabled />
    </el-descriptions-item>
</el-descriptions>

后果: 触发动态展示后,节点 b、c 均变为不可编辑 ,表现与下方 disabled 字段一致,但 v-model 数据绑定本身并未出错。

根因(Diff 复用路径):key 时,列表从 [a, d, e] 变为 [a, b, c, d, e],新插入的可编辑输入框复用了后方已存在的 disabled 输入框 DOM,仅更新了标签等表层信息,却保留了 disabled 属性------这就是"属性继承"问题的本质。

六、正确做法:为条件渲染节点加唯一 key

让 Vue 将其识别为独立节点,避免与下方 disabled 输入框发生就地复用:

vue 复制代码
<el-descriptions-item label="节点 b" v-if="showFieldB" key="editable-b">
    <el-input v-model="row.b" style="width: 100%" />
</el-descriptions-item>
<el-descriptions-item label="节点 c" v-if="showFieldC" key="editable-c">
    <el-input v-model="row.c" style="width: 100%" />
</el-descriptions-item>

实践建议:

  • 优先将 key 加在带 v-if外层节点上,语义更清晰,也更贴合 Diff 场景。
  • key 加在内部 el-input 上同样有效,但外层加 key 是更推荐的做法。

七、可验证示例:key 如何影响 Diff

vue 复制代码
<template>
  <div>
    <!-- 无 key:可能复用 DOM,导致 disabled 被继承 -->
    <div v-for="item in listWithoutKey" class="item">
      <input :disabled="item.disabled" placeholder="无 key" />
    </div>

    <hr />

    <!-- 有 key:精准插入,避免错误复用 -->
    <div v-for="item in listWithKey" :key="item.id" class="item">
      <input :disabled="item.disabled" placeholder="有 key" />
    </div>

    <button @click="showBC">显示 BC</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      listWithoutKey: [
        { name: 'a', disabled: false },
        { name: 'd', disabled: true },
        { name: 'e', disabled: true }
      ],
      listWithKey: [
        { id: 1, name: 'a', disabled: false },
        { id: 4, name: 'd', disabled: true },
        { id: 5, name: 'e', disabled: true }
      ]
    };
  },
  methods: {
    showBC() {
      this.listWithoutKey.splice(1, 0,
        { name: 'b', disabled: false },
        { name: 'c', disabled: false }
      );

      this.listWithKey.splice(1, 0,
        { id: 2, name: 'b', disabled: false },
        { id: 3, name: 'c', disabled: false }
      );
    }
  }
};
</script>

<style scoped>
.item { margin: 5px 0; }
input { margin-left: 10px; }
</style>

key 时,Vue 通过 id 映射直接插入 b/c,d/e 的 DOM 不被复用,属性保持正确。

八、总结

要点 说明
核心机制 Vue Diff 采用同层比较 + key 优化,以最小 DOM 操作完成更新
key 的作用 标识 VNode 身份,让 Vue 精准定位是"复用"还是"新建"
不加 key 的后果 v-if/v-for 动态插入同级节点时,DOM 就地复用,新节点可能继承旧属性(如 disabled
正确做法 为条件渲染节点添加唯一 key,强制 Vue 创建独立 DOM
适用范围 不仅限于 v-forv-if 动态插入的同级节点同样需要 key
相关推荐
李剑一8 小时前
ant-design-vue 1.x 版本换肤功能开发
前端·vue.js·ant design
李明卫杭州9 小时前
Vue2 与 Vue3 自定义指令详解:钩子变化、迁移指南与实战示例
前端·javascript·vue.js
nicole bai11 小时前
vue 自定义模块内容
前端·javascript·vue.js
遇乐的果园13 小时前
前端学习笔记-vue列表处理
前端·vue.js·学习
ss27313 小时前
Vue 完整版本发展史时间线(2013–2026)|版本迭代里程碑+核心特性对照表
前端·javascript·vue.js
英勇无比的消炎药13 小时前
体积砍半、加载提速、请求不卡:TinyRobot 生产环境性能调优全攻略
vue.js
gis开发之家13 小时前
《Vue3 从入门到大神32篇》Vue3 源码详解(二):从 GitHub Clone 到断点调试,手把手搭建源码阅读环境
vue.js·前端框架·github·vue3·vue3源码
徐小超15 小时前
从0到1落地AI知识问答系统(一):AI结对协作实战技巧
vue.js·node.js·全栈
摇滚侠1 天前
SpringBoot3+Vue3 全套视频教程 45-51
前端·javascript·vue.js
星河耀银海1 天前
框架结合:Vue+HTML5+AI实现智能前端应用开发
前端·vue.js·html5