跳转到内容

Vue 原理

Vue 3 的 Diff 算法是虚拟 DOM 更新的核心机制,主要通过 patchChildrenpatchKeyedChildren 函数实现。相比 Vue 2,Vue 3 引入了更高效的算法优化。

Vue 3 的 Diff 算法采用分层比较策略,具体流程如下:

  • 如果新旧节点类型不同(如 div vs span),直接替换整个节点
  • 如果节点类型相同,进入子节点比较阶段

根据新旧子节点的类型,Vue 3 采用不同的处理策略:

// Vue 3 内部 patchChildren 简化逻辑
function patchChildren(n1, n2, container) {
const c1 = n1 && n1.children
const c2 = n2.children
if (typeof c2 === 'string') {
// 新子节点是文本
if (Array.isArray(c1)) {
unmountChildren(c1) // 卸载旧的子节点数组
}
if (c2 !== c1) {
hostSetElementText(container, c2) // 设置文本内容
}
} else if (Array.isArray(c2)) {
if (Array.isArray(c1)) {
// 新旧都是数组,进入核心 diff
patchKeyedChildren(c1, c2, container)
} else {
// 旧的是文本,新的是数组
hostSetElementText(container, '')
mountChildren(c2, container) // 挂载新的子节点
}
}
}

当新旧子节点都是数组时,Vue 3 使用优化的双端比较 + 最长递增子序列算法:

第一步:同步头部节点

// 从左往右比较,直到遇到不同的节点
let i = 0
while (i < c1.length && i < c2.length) {
if (isSameVNodeType(c1[i], c2[i])) {
patch(c1[i], c2[i], container)
i++
} else {
break
}
}

第二步:同步尾部节点

// 从右往左比较
let e1 = c1.length - 1
let e2 = c2.length - 1
while (i <= e1 && i <= e2) {
if (isSameVNodeType(c1[e1], c2[e2])) {
patch(c1[e1], c2[e2], container)
e1--
e2--
} else {
break
}
}

第三步:处理剩余节点

经过前两步后,可能出现三种情况:

  1. 新增节点:新子节点有剩余,旧子节点已处理完
if (i > e1) {
if (i <= e2) {
// 挂载新增的节点
while (i <= e2) {
patch(null, c2[i], container)
i++
}
}
}
  1. 删除节点:旧子节点有剩余,新子节点已处理完
else if (i > e2) {
while (i <= e1) {
unmount(c1[i]) // 卸载多余的旧节点
i++
}
}
  1. 复杂情况:新旧都有剩余节点,需要进行核心 Diff

对于最复杂的情况,Vue 3 使用了最长递增子序列(LIS)算法来最小化 DOM 移动:

// 构建新子节点的 key -> index 映射
const keyToNewIndexMap = new Map()
for (i = s2; i <= e2; i++) {
const nextChild = c2[i]
if (nextChild.key != null) {
keyToNewIndexMap.set(nextChild.key, i)
}
}
// 记录新节点在旧序列中的位置
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
// 遍历旧子节点,建立位置映射
for (i = s1; i <= e1; i++) {
const prevChild = c1[i]
const newIndex = keyToNewIndexMap.get(prevChild.key)
if (newIndex === undefined) {
unmount(prevChild) // 在新序列中不存在,删除
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
patch(prevChild, c2[newIndex], container)
}
}
// 计算最长递增子序列
const increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
// 基于 LIS 进行最少的 DOM 移动
let j = increasingNewIndexSequence.length - 1
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex]
if (newIndexToOldIndexMap[i] === 0) {
// 新增节点
patch(null, nextChild, container)
} else if (i !== increasingNewIndexSequence[j]) {
// 需要移动的节点
move(nextChild, container)
} else {
// 在 LIS 中,不需要移动
j--
}
}

Vue 3 的 Diff 算法相比 Vue 2 有以下优势:

  1. 最长递增子序列优化:通过 LIS 算法找到最长的不需要移动的节点序列,最小化 DOM 操作
  2. 双端比较:先处理头尾相同的节点,减少核心 diff 的工作量
  3. 静态标记:结合编译时的 PatchFlags,实现靶向更新
  4. 更好的时间复杂度:在大多数实际场景下性能更优

可以参考下面的图辅助理解:

Diff算法

Vue 3 的 Diff 算法是虚拟 DOM 更新的核心机制,采用分层比较策略来高效更新真实 DOM。

整个流程分为四个步骤:首先进行节点类型比较,如果类型不同直接替换整个节点;

然后根据子节点类型采用不同策略处理,文本节点直接更新内容,数组节点进入核心 Diff;

接着使用双端比较算法,从头尾两端同时向中间遍历,先处理相同的头部和尾部节点;

最后对剩余的复杂情况使用最长递增子序列(LIS)算法优化,通过建立 key 到索引的映射关系,找出最长的不需要移动的节点序列,最小化 DOM 移动操作。

相比 Vue 2,Vue 3 还结合了编译时的 PatchFlags 静态标记和 Block Tree 优化,实现了靶向更新,显著提升了渲染性能。

静态提升 (Static Hoisting) 将模板中永远不会改变的静态节点“提升”到渲染函数之外,从而避免不必要的重复创建和比对开销。

Vue模板中,那些没有绑定任何响应式数据(没有 v-bind, v-model, {{ }} 等)的节点就是静态节点。比如一个普通的<div>Hello World</div>Vue3的编译器会识别出这些静态节点,并进行如下的优化:

  • “提升”到外部: 编译器会在编译阶段,只创建一次这些静态节点的VNode

  • 重用时直接引用: 当组件再次渲染时,渲染函数不再重新创建这些静态节点的VNode,而是直接重用之前创建好的那一个。

  • 跳过Diff: 因为每次渲染用的都是同一个VNode对象,在diff算法进行新旧节点对比时,由于oldVNode === newVNode成立,算法会直接跳过这个节点及其整个子树的比对,因为它知道这里绝对不可能发生变化。

Patch Flags 是 Vue 3 编译时优化的核心机制,编译器在生成 VNode 时会为每个动态节点添加标志位,明确告诉运行时该节点的哪些部分可能发生变化。

基于 Vue 3 源码,主要的 PatchFlags 包括:

// Vue 3 源码中的 PatchFlags 定义
export const enum PatchFlags {
TEXT = 1, // 动态文本内容
CLASS = 1 << 1, // 动态 class
STYLE = 1 << 2, // 动态 style
PROPS = 1 << 3, // 动态属性(除 class/style 外)
FULL_PROPS = 1 << 4, // 有 key,需要完整 diff
HYDRATE_EVENTS = 1 << 5, // 有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 稳定序列,子节点顺序不会改变
KEYED_FRAGMENT = 1 << 7, // 有 key 的 fragment
UNKEYED_FRAGMENT = 1 << 8, // 无 key 的 fragment
NEED_PATCH = 1 << 9, // 只需要非 props 比较
DYNAMIC_SLOTS = 1 << 10, // 动态插槽
DEV_ROOT_FRAGMENT = 1 << 11, // 开发模式下的根 fragment
HOISTED = -1, // 静态提升的节点
BAIL = -2 // diff 算法无法优化,退回完整 diff
}

Vue 3 编译器会分析模板,为动态节点生成对应的 PatchFlags:

// 模板
<div :class="dynamicClass">{{ message }}</div>
// 编译后生成的渲染函数
function render() {
return createVNode("div", {
class: _ctx.dynamicClass
}, _toDisplayString(_ctx.message),
3 /* TEXT | CLASS */) // PatchFlag = 1 | 2 = 3
}

在 Diff 过程中,Vue 3 会根据 PatchFlags 进行靶向更新:

// Vue 3 内部 patchElement 简化逻辑
function patchElement(n1, n2, container) {
const el = n2.el = n1.el
const oldProps = n1.props || {}
const newProps = n2.props || {}
const { patchFlag } = n2
if (patchFlag > 0) {
// 有 PatchFlag,进行靶向更新
if (patchFlag & PatchFlags.CLASS) {
// 只更新 class
if (oldProps.class !== newProps.class) {
hostPatchProp(el, 'class', null, newProps.class)
}
}
if (patchFlag & PatchFlags.STYLE) {
// 只更新 style
hostPatchProp(el, 'style', oldProps.style, newProps.style)
}
if (patchFlag & PatchFlags.TEXT) {
// 只更新文本内容
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children)
}
}
if (patchFlag & PatchFlags.PROPS) {
// 更新其他动态属性
const propsToUpdate = n2.dynamicProps
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const prev = oldProps[key]
const next = newProps[key]
if (next !== prev) {
hostPatchProp(el, key, prev, next)
}
}
}
} else if (patchFlag === PatchFlags.FULL_PROPS) {
// 需要完整的 props diff
patchProps(el, n2, oldProps, newProps)
}
// 处理子节点
patchChildren(n1, n2, el)
}

PatchFlags 机制带来的性能提升:

  1. 跳过静态内容:标记为 HOISTED 的节点完全跳过 diff
  2. 靶向更新:只检查标记的动态部分,避免全量属性比较
  3. 减少遍历:不需要遍历所有属性,直接定位到变化的部分
  4. 编译时优化:在编译阶段就确定了运行时的优化策略
<template>
<div class="static-class" :class="dynamicClass">
<p>Static text</p>
<p>{{ dynamicText }}</p>
<button @click="handleClick">{{ buttonText }}</button>
</div>
</template>

编译后的优化效果:

  • 外层 div:CLASS flag,只检查 class 变化
  • 第一个 p:静态提升,跳过 diff
  • 第二个 p:TEXT flag,只检查文本内容
  • button:TEXT | PROPS flag,检查文本和事件属性

这种精确的标记机制使得 Vue 3 的更新性能相比 Vue 2 有了显著提升。

Vue 3 的虚拟 DOM 优化主要包括三个方面:静态提升、补丁标志和 Block Tree。

静态提升将模板中永不变化的静态节点提升到渲染函数外部,避免重复创建和 Diff 比较;补丁标志(PatchFlags)在编译时为每个动态节点添加标志位,明确标识哪些部分可能变化,实现靶向更新而非全量比较;

Block Tree 将动态节点收集到扁平数组中,跳过静态节点的遍历。

这些优化使得 Vue 3 在编译时就能确定运行时的更新策略,相比 Vue 2 的全量 Diff,性能提升显著,特别是在大型应用中效果更加明显。

虚拟 DOM(Virtual DOM)是对真实 DOM 的 JavaScript 抽象表示。在 Vue 3 中,虚拟 DOM 节点(VNode)是一个普通的 JavaScript 对象,包含了描述真实 DOM 节点所需的所有信息。

// Vue 3 中的 VNode 结构(简化版)
interface VNode {
type: string | Component | Symbol // 节点类型
props: Record<string, any> | null // 属性
children: VNodeChildren // 子节点
key: string | number | symbol | null // 用于 diff 的 key
ref: VNodeRef | null // 引用
el: Element | null // 对应的真实 DOM 元素
patchFlag: number // 补丁标志
dynamicProps: string[] | null // 动态属性列表
shapeFlag: number // 形状标志,用于快速判断节点类型
}

Vue 3 提供了多种创建 VNode 的方式:

// 使用 h 函数创建 VNode
import { h } from 'vue'
// 创建元素节点
const vnode1 = h('div', { class: 'container' }, 'Hello World')
// 创建组件节点
const vnode2 = h(MyComponent, { prop: 'value' })
// 创建文本节点
const vnode3 = h(Text, 'Pure text')
// 创建注释节点
const vnode4 = h(Comment, 'This is a comment')

Vue 3 的渲染流程可以分为以下几个阶段:

模板编译器将 Vue 模板转换为渲染函数:

// 模板
<template>
<div class="container">
<h1>{{ title }}</h1>
<p v-if="showContent">{{ content }}</p>
</div>
</template>
// 编译后的渲染函数(简化)
function render(_ctx) {
return h('div', { class: 'container' }, [
h('h1', null, _ctx.title, 1 /* TEXT */),
_ctx.showContent
? h('p', null, _ctx.content, 1 /* TEXT */)
: null
])
}

渲染函数执行,生成 VNode 树:

// Vue 3 内部渲染流程(简化)
function renderComponentRoot(instance) {
const { render, data, props } = instance
// 设置当前渲染实例
setCurrentRenderingInstance(instance)
try {
// 执行渲染函数,生成 VNode 树
const result = render.call(data, data, props)
return result
} finally {
setCurrentRenderingInstance(null)
}
}

将 VNode 转换为真实 DOM 并插入到页面:

// Vue 3 内部挂载逻辑(简化)
function mountElement(vnode, container) {
const { type, props, children, patchFlag } = vnode
// 创建真实 DOM 元素
const el = document.createElement(type)
vnode.el = el
// 处理属性
if (props) {
for (const key in props) {
patchProp(el, key, null, props[key])
}
}
// 处理子节点
if (typeof children === 'string') {
el.textContent = children
} else if (Array.isArray(children)) {
mountChildren(children, el)
}
// 插入到容器
container.appendChild(el)
}

当响应式数据变化时,触发重新渲染和 Diff:

// Vue 3 内部更新逻辑(简化)
function updateComponent(instance) {
const { render, update } = instance
// 生成新的 VNode 树
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
// 执行 patch(diff + 更新)
patch(prevTree, nextTree, instance.container)
}

Vue 3 采用组件级别的更新策略,只有当组件的响应式数据发生变化时,才会重新渲染该组件:

// 响应式数据变化触发组件更新
function triggerEffect(effect) {
if (effect.scheduler) {
// 使用调度器异步更新
effect.scheduler()
} else {
// 同步执行副作用
effect.run()
}
}

Vue 3 使用异步更新队列来批量处理更新,避免同一个 tick 内的重复渲染:

// Vue 3 内部更新队列(简化)
const queue = []
let isFlushing = false
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
}
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(flushJobs)
}
}
function flushJobs() {
isFlushing = false
queue.sort((a, b) => a.id - b.id) // 按组件层级排序
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
queue.length = 0
}

Vue 3 引入了 Block Tree 概念,将动态节点收集到一个扁平的数组中,跳过静态节点的遍历:

// Block Tree 示例
function render() {
return (openBlock(), createElementBlock('div', null, [
createElementVNode('h1', null, 'Static Title'),
createElementVNode('p', null, toDisplayString(dynamicText), 1 /* TEXT */)
]))
}
// 只有带有 PatchFlag 的节点会被收集到 dynamicChildren 中
// 在 diff 时直接遍历 dynamicChildren,跳过静态节点

相比传统的直接 DOM 操作,虚拟 DOM 的优势:

  1. 批量更新:将多次 DOM 操作合并为一次
  2. 最小化操作:通过 Diff 算法找出最小变更集
  3. 跨平台抽象:可以渲染到不同的平台(Web、Native、SSR)
  4. 开发体验:提供声明式的编程模型

Vue 3 相比 Vue 2 的虚拟 DOM 优化:

  • 编译时优化:PatchFlags、静态提升、Block Tree
  • 更高效的 Diff:最长递增子序列、双端比较
  • 更小的运行时:Tree-shaking 友好的架构
  • 更好的 TypeScript 支持:完整的类型推导
<template>
<div class="static-class" :class="dynamicClass">
<p>Static text</p>
<p>{{ dynamicText }}</p>
<button @click="handleClick">{{ buttonText }}</button>
</div>
</template>

编译后的优化效果:

  • 外层 div:CLASS flag,只检查 class 变化
  • 第一个 p:静态提升,跳过 diff
  • 第二个 p:TEXT flag,只检查文本内容
  • button:TEXT | PROPS flag,检查文本和事件属性

这种精确的标记机制使得 Vue 3 的更新性能相比 Vue 2 有了显著提升。

Vue 3 的虚拟 DOM 是对真实 DOM 的 JavaScript 抽象表示,通过 VNode 对象描述 DOM 结构。

渲染机制分为四个阶段:

  • 编译阶段将模板转换为渲染函数,渲染阶段执行渲染函数生成 VNode 树,

  • 挂载阶段将 VNode 转换为真实 DOM 并插入页面,

  • 更新阶段通过 Diff 算法比较新旧 VNode 树并最小化 DOM 操作。Vue 3 的优化包括组件级更新(只更新数据变化的组件)、异步更新队列(批量处理更新避免重复渲染)、Block Tree 优化(跳过静态节点直接处理动态节点)等。

相比直接操作 DOM,虚拟 DOM 提供了更好的性能、跨平台能力和开发体验。

ref的实现中,对外层的value对象采用getter/setters进行读写追踪,如:

function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(refObject, 'value')
}
}
return refObject
}

reactive的实现中,对象类型数据的响应式实现采用Proxy/Reflect实现。

Proxy:对目标对象进行代理,拦截其操作(如读取get、修改set、删除deleteProperty等)。

Reflect:操作对象的工具方法,与Proxy拦截器一一对应,保证操作行为的默认正确性。

使用例子如下:

const proxy = new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
});
  1. Proxy可以代理整个对象,而defineProperty只能劫持对象的属性
  • 使用Object.defineProperty时,Vue2必须遍历对象的每一个属性,递归地将其转换为gettersetter。这意味着它无法探测到对象新增的属性和删除的属性(所以才需要Vue.setVue.delete这些API)。

  • Proxy是对象级别的代理。它不需要遍历所有属性,它可以监听整个对象。因此,直接给对象添加新属性、删除现有属性,甚至通过索引直接设置数组的新元素,这些操作都能被Proxy捕获到。这让Vue3的响应式系统变得非常自然,开发者几乎不需要再关心特殊的API,直接用原生JS的方式操作数据即可。

  1. Proxy能监听数组的变化,无需重写数组方法
  • Vue2中,为了监听数组的pushpopsplice等变化,不得不重写这些数组方法,并在其中加入通知更新的逻辑。这对于开发者和运行时的性能都是一种开销。

  • Proxy可以直接监听数组索引的变化和length的变化。因此,在Vue3中,我们可以直接通过索引修改数组(如arr[1] = newValue)或者直接修改length,这些操作都能触发响应式更新,这是一个非常大的便利性提升。

  1. Proxy性能更好
  • Object.defineProperty需要在一开始就递归遍历整个对象来完成劫持,这个初始化过程在复杂对象上是有性能成本的。

  • Proxy是在对象层面进行代理,初始化速度通常更快。此外,Vue3利用了Proxy的特性实现了更高效的依赖收集和跟踪。例如,它只在真正访问到某个属性时才去递归代理其内嵌对象(惰性代理),而不是一开始就全部递归完成,这提升了初始化的性能。

  1. Proxy拦截操作更为丰富
  • Object.defineProperty只能拦截属性的读取(get) 和设置(set)。

  • Proxy提供了多达13种拦截器,例如has(拦截in操作符)、deleteProperty(拦截delete操作符)、ownKeys(拦截Object.keys()等)。这使得Vue3能够实现更全面、更精确的响应式跟踪。

Vue 3 的响应式系统基于 Proxy 和 Reflect 实现,相比 Vue 2 的 Object.defineProperty 有显著优势。

对于基本类型数据,使用 ref 函数通过 getter/setter 包装成响应式对象;对于对象类型数据,使用 reactive 函数通过 Proxy 代理拦截所有操作。核心机制是依赖收集和派发更新:在 getter 中通过 track 函数收集依赖,在 setter 中通过 trigger 函数触发更新。

Proxy 相比 Object.defineProperty 的优势包括:能够监听数组变化、支持动态添加删除属性、更好的性能表现、支持 Map/Set 等数据结构。整个响应式系统通过 effect 函数建立响应式副作用,实现了数据变化到视图更新的自动化流程。

在列表渲染中,正确使用 key 属性对 Diff 算法的性能至关重要:

<!-- ❌ 不推荐:使用索引作为 key -->
<li v-for="(item, index) in list" :key="index">
{{ item.name }}
</li>
<!-- ✅ 推荐:使用唯一标识作为 key -->
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>

原因:使用唯一标识作为 key 可以让 Vue 准确识别节点的变化,避免不必要的 DOM 操作。

对于不需要响应式的数据,使用 markRawshallowRef 来避免深度响应式转换:

import { markRaw, shallowRef, reactive } from 'vue'
// ❌ 不推荐:对大型静态数据进行深度响应式转换
const largeStaticData = reactive({
config: { /* 大量配置数据 */ },
constants: { /* 常量数据 */ }
})
// ✅ 推荐:标记为非响应式
const largeStaticData = markRaw({
config: { /* 大量配置数据 */ },
constants: { /* 常量数据 */ }
})
// ✅ 推荐:使用浅层响应式
const shallowData = shallowRef({
level1: { level2: { level3: 'value' } }
})

Vue 3.2+ 引入的 v-memo 指令可以缓存模板的一部分:

<template>
<div v-for="item in list" :key="item.id">
<!-- 只有当 item.id 或 item.name 变化时才重新渲染 -->
<div v-memo="[item.id, item.name]">
<h3>{{ item.name }}</h3>
<p>{{ item.description }}</p>
<!-- 复杂的子组件 -->
<ComplexComponent :data="item" />
</div>
</div>
</template>

将大型组件拆分为更小的组件,并使用异步组件进行懒加载:

// ❌ 不推荐:单个大型组件
export default {
components: {
HeavyComponent: () => import('./HeavyComponent.vue')
}
}
// ✅ 推荐:组件拆分 + 懒加载
export default {
components: {
Header: () => import('./components/Header.vue'),
Content: () => import('./components/Content.vue'),
Footer: () => import('./components/Footer.vue')
}
}

对于频繁切换的组件,使用 KeepAlive 进行缓存:

<template>
<KeepAlive :include="['ComponentA', 'ComponentB']">
<component :is="currentComponent" />
</KeepAlive>
</template>
import { computed, watch, watchEffect } from 'vue'
// ✅ 推荐:使用计算属性缓存复杂计算
const expensiveValue = computed(() => {
return heavyCalculation(props.data)
})
// ✅ 推荐:使用 watchEffect 进行自动依赖收集
watchEffect(() => {
console.log('Data changed:', props.data)
})
// ✅ 推荐:使用 watch 的 lazy 选项
watch(
() => props.data,
(newVal) => {
// 处理变化
},
{ lazy: true } // 首次不执行
)
// 性能测试工具函数
function measurePerformance(name, fn) {
const start = performance.now()
fn()
const end = performance.now()
console.log(`${name}: ${end - start}ms`)
}
// 测试 Diff 算法性能
function testDiffPerformance() {
const largeList = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
value: Math.random()
}))
measurePerformance('Large List Render', () => {
// 模拟大列表渲染
const vnode = h('div',
largeList.map(item =>
h('div', { key: item.id }, item.name)
)
)
})
}
// 测试响应式性能
function testReactivityPerformance() {
const data = reactive({
items: Array.from({ length: 1000 }, (_, i) => ({ id: i, value: i }))
})
measurePerformance('Reactivity Update', () => {
data.items.forEach(item => {
item.value = Math.random()
})
})
}

使用 Vue DevTools 进行性能分析:

// 在开发环境中启用性能追踪
if (process.env.NODE_ENV === 'development') {
app.config.performance = true
}
// 使用 performance API 进行自定义测量
function trackComponentPerformance(componentName) {
performance.mark(`${componentName}-start`)
return () => {
performance.mark(`${componentName}-end`)
performance.measure(
`${componentName}-duration`,
`${componentName}-start`,
`${componentName}-end`
)
}
}

Vue 3 的 Diff 算法和渲染系统通过以下优化实现了显著的性能提升:

  1. 编译时优化:PatchFlags、静态提升、Block Tree
  2. 运行时优化:最长递增子序列、双端比较、组件级更新
  3. 开发者工具:v-memo、KeepAlive、异步组件等

通过合理使用这些特性和遵循最佳实践,可以构建高性能的 Vue 3 应用程序。

Vue 3 性能优化主要从几个方面入手:

  • 首先是合理使用 key 属性,在列表渲染中使用唯一标识而非索引作为 key,让 Diff 算法准确识别节点变化;其次是避免不必要的响应式转换,对静态数据使用 markRaw 或 shallowRef;
  • 然后是善用 v-memo 指令缓存子树渲染结果,减少重复计算;还要进行组件拆分和懒加载,避免单个组件过于庞大;
  • 使用 KeepAlive 缓存组件状态;
  • 优化计算属性和侦听器的使用,避免不必要的重复计算。

此外,Vue 3 本身通过编译时优化(静态提升、PatchFlags)和运行时优化(Proxy 响应式、Fragment 支持)显著提升了性能,开发者还可以使用 Vue DevTools 进行性能分析和调试。