Vue 3 比 Vue 2 有哪些优化升级?
很多同学第一次从 Vue 2 升级到 Vue 3,会把注意力放在「API 变了(createApp、Composition API、v-model…)」上,但 Vue 3 更大的价值其实在于:底层架构和编译策略的升级,让它在“运行更快、包更小、类型更友好、可维护性更强”。
本文会用“能落地的角度”把 Vue 3 相比 Vue 2 的优化点串起来,并在最后给出面试高频问答。
1. 快速对照表:Vue 3 升级了什么?
| 维度 | Vue 2 | Vue 3(优化点) | 你能直接感受到的变化 |
|---|---|---|---|
| 响应式系统 | Object.defineProperty(属性级) | Proxy + effect(对象级) | 新增/删除属性、数组索引、Map/Set 变更可追踪;依赖更精确 |
| 编译器 | 更偏“运行时兜底” | 更偏“编译期算清楚” | 更新时少做判断:PatchFlag、静态提升、事件缓存、Block Tree |
| Diff / VDOM | 双端 Diff(运行时需要更多对比) | 更快 Diff + 编译优化配合 | 列表重排、动态节点更新更快,避免无意义对比 |
| 包体积 | 更偏整体打包 | 更强 Tree-Shaking(按需) | 业务只用到的能力才进包,产物更小 |
| TS 支持 | “补丁式”类型增强 | 源码用 TS 重写,类型推导更强 | 组件 Props/Emits/Slots 的类型更准确,IDE 体验更好 |
| 组织方式 | Options API 为主 | Options + Composition 并存 | 复杂逻辑更好拆分复用,避免 mixins 的命名冲突 |
| 浏览器支持 | 可兼容 IE11(成本高) | 放弃 IE11(关键前提) | 可以彻底拥抱现代 JS:Proxy、更简洁调度器、更少降级分支 |
Vue 3 的性能提升可以用一句话概括:“更强的响应式 + 更聪明的编译器 + 更少的运行时工作”。
2. 响应式系统:从 defineProperty 到 Proxy(为什么更快、更全)
Vue 2 的响应式核心是 Object.defineProperty:给每个属性加 getter/setter。它能工作,但有天然局限:
- 只能拦截“已存在的属性”,新增/删除属性需要
Vue.set / Vue.delete - 数组索引赋值、改
length难以直接追踪,需要重写数组方法 - 初始化时需要递归遍历深层对象,数据很大时开销明显
Map/Set等数据结构无法很好支持
Vue 3 用 Proxy 做对象级代理,能拦截更多操作(读/写/删/遍历…),配合 track/trigger/effect 形成更精确的依赖图。
2.1 一个最直观的例子:新增属性不再需要 Vue.set
Vue 2:
// Vue 2:新增属性不是响应式的
this.user.age = 18 // ❌ 视图不更新
this.$set(this.user, 'age', 18) // ✅
Vue 3:
// Vue 3:Proxy 能拦截到新增属性
const user = reactive({ name: '张三' })
user.age = 18 // ✅ 视图可更新
2.2 Vue 3 响应式的心智模型:track / trigger / effect
你可以把 Vue 3 的响应式理解成三句话:
- effect:把“渲染函数 / 计算属性 / watch 回调”包成一个可重新执行的函数
- track:读取响应式数据时,记录“谁用到了我”
- trigger:修改响应式数据时,通知“所有用到我的 effect 重新跑”
如果你想把响应式系统从“能用”学到“能讲清”,建议配合阅读:
3. 编译器优化:把“能提前算出来的”都前置到编译期
Vue 2 的思路更偏“运行时兜底”:运行时需要做更多判断,更新时更容易走到“全量对比”的路径。
Vue 3 的核心思路是:模板编译阶段尽量分析清楚哪些是动态的,把信息编码进渲染函数,让运行时做更少工作。
3.1 PatchFlag:告诉运行时“只更新这些地方”
举个例子:
<template>
<div class="card">
<h3>{{ title }}</h3>
<p>作者:Vue</p>
</div>
</template>
这里真正会变化的只有 {{ title }}。Vue 3 编译器会给对应的 VNode 打上“动态文本”的标记(PatchFlag),运行时 patch 时就不会反复比对整棵树。
3.2 静态提升:把“永远不变”的节点挪到渲染函数外
像 <p>作者:Vue</p> 这种完全静态的节点,在 Vue 2 中每次重新渲染都会创建一遍(哪怕最终能复用 DOM,也会产生 JS 层面的对象创建开销)。
Vue 3 会把它提升成一个常量(hoist),后续渲染直接复用这份 VNode 描述。
3.3 Block Tree:只收集“动态后代”,避免深层遍历
Vue 3 引入 Block 的概念:一个 Block 会把它的所有动态子节点收集到 dynamicChildren 中,更新时直接遍历这份“动态列表”,不再无脑深搜整棵树。
编译器优化是 Vue 3 性能提升的关键原因之一,推荐阅读:
4. Diff 与更新策略:更快的列表更新 + 更少的无意义对比
Vue 2 的列表 Diff 以“双端 Diff”为代表;Vue 3 在此基础上结合编译优化与更快的 Diff 策略(例如利用最长递增子序列来减少移动),在常见场景下能做到更少的 DOM 操作。
你在业务里最常见的体感来自两点:
- 同样是列表重排(带 key):Vue 3 更容易做到“少移动、少卸载”
- 同样是局部动态更新:Vue 3 因为有 PatchFlag/BlockTree,经常能避免很多无意义的节点比对
5. 包体积与 Tree-Shaking:按需引入,产物更小
Vue 3 的能力拆得更细(响应式、运行时核心、DOM 运行时、编译器等都是独立模块),并且优先面向 ESM 设计,配合现代打包器就能把“没用到的功能”摇掉。
你在业务里的直接收益通常是:
- 只用到
ref/reactive/computed的页面,不会把一堆没用到的能力一起打进包 - 产物更小,首屏加载更快(尤其在移动端更明显)
6. 代码组织升级:Composition API 不是“替代”,而是“更适合复杂场景”
Vue 2 也能写复杂业务,但当逻辑跨多个模块复用时,常见方案是 mixins / 高阶组件 / render props,这些方案要么容易命名冲突,要么类型难推导。
Composition API 的核心收益有两点:
- 按“功能”聚合代码(登录态、权限、表单、请求…),而不是按“选项”散落在 data/methods/watch 里
- 更自然地复用逻辑(抽
useXxx),同时 TS 能推断得更准确
简单对比一下“复用登录态”:
// Vue 3:Composition API(示意)
export function useAuth() {
const token = ref<string | null>(null)
const isLogin = computed(() => Boolean(token.value))
function login(newToken: string) {
token.value = newToken
}
return { token, isLogin, login }
}
在组件里直接用:
<script setup lang="ts">
import { useAuth } from './useAuth'
const { isLogin, login } = useAuth()
</script>
7. TypeScript 体验:从“能写 TS”到“TS 是一等公民”
Vue 3 源码用 TypeScript 重写后,组件类型能力明显增强,典型体现在:
defineProps/defineEmits的推导更好(更少any)- 事件、插槽的类型约束更可控
- 对大型项目(多人协作、组件库)更友好
<script setup lang="ts">
const props = defineProps<{ count: number }>()
const emit = defineEmits<{ (e: 'change', v: number): void }>()
function inc() {
emit('change', props.count + 1)
}
</script>
8. 工程化与生态:围绕 Vue 3 的“现代默认”
严格来说,这不全是“框架本体”的优化,但对真实项目的体验提升非常明显:
- 构建工具更现代:Vite 作为主流选择,基于 ESM 的开发服务器 + 更快的 HMR,配合 Vue 3 的编译体系体验更顺滑
- 路由与状态管理更贴合 TS:Vue Router 4、Pinia 都对类型推导更友好,也更容易做按需拆包
- SSR 体验更完善:更容易做出“更快的首屏 + 更小的 hydration 成本”(依赖编译期信息与更轻的运行时)
9. 升级与迁移:你需要特别注意的“破坏性变化”
优化升级通常伴随一些不兼容点,常见的坑可以先记住这几类:
- 应用入口变了:
new Vue()→createApp() - 全局 API 调整:注册组件/指令/插件的方式更偏“app 实例级”
- 部分语法/能力移除或调整:
filters移除、.native移除、v-model语义升级等 - 生命周期命名调整:
beforeDestroy/destroyed→beforeUnmount/unmounted - 浏览器兼容:由于
Proxy无法被完整 polyfill,Vue 3 不支持 IE11
9.1 入口与全局 API:new Vue → createApp
Vue 2:
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
Vue 3:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
如果你在 Vue 2 里经常用全局注册:
// Vue 2
Vue.component('BaseButton', BaseButton)
Vue 3 推荐绑定到 app 实例上(更“可控”):
// Vue 3
const app = createApp(App)
app.component('BaseButton', BaseButton)
app.mount('#app')
9.2 v-model 的默认协议变了(但更强了)
Vue 2 中,v-model 默认对应:
- prop:
value - event:
input
Vue 3 中,默认对应:
- prop:
modelValue - event:
update:modelValue
同时 Vue 3 支持“多个 v-model”:
<MyForm v-model:title="title" v-model:content="content" />
如果是存量 Vue 2 项目,尽量遵循“先跑起来、再逐个模块迁移”的节奏;对大项目可考虑使用兼容构建(compat build)作为过渡,避免一次性大改导致风险失控。
10. 面试高频问答
Q1:Vue 3 为什么一定要用 Proxy?defineProperty 不行吗?
因为 Object.defineProperty 是“属性级别”的拦截,天然拦不住新增/删除属性、数组索引赋值、Map/Set 等操作;而 Proxy 是“对象级别”的代理,可以覆盖更多语义,同时还能做“懒代理”降低初始化成本。代价是:Proxy 无法被完整 polyfill,所以 Vue 3 放弃 IE11。
Q2:Vue 3 响应式里的 track/trigger/effect 分别是什么?
effect:把副作用(渲染、计算、watch)包装起来,依赖变化时重跑track:读取响应式数据时收集依赖(把 effect 记到依赖集合里)trigger:写入/删除等变更发生时触发依赖(把相关 effect 调度执行)
Q3:Vue 3 的编译器做了哪些关键优化?(至少说 3 个)
常见回答点:
- PatchFlag:标记动态绑定,运行时靶向更新
- 静态提升:把完全静态的 VNode 提升为常量复用
- 事件处理缓存:避免每次渲染都创建新函数导致子组件重复更新
- Block Tree:收集动态子节点列表,更新时避免深层遍历
Q4:为什么说 Vue 3“运行时更轻”?
因为 Vue 3 把很多“判断工作”提前到编译期完成(例如哪些是动态的、哪些可以提升复用),运行时 patch 时不需要全量遍历与对比,常见路径能更快走完。
Q5:Vue 3 的 Diff 为什么更快?
两方面共同作用:
- Diff 算法本身更优化(常见实现会利用最长递增子序列减少移动)
- 编译器给了运行时更多信息(PatchFlag/BlockTree),让 Diff 的作用范围变小
Q6:Composition API 相比 mixins 的核心优势是什么?
- 避免 mixins 的命名冲突与“隐式依赖”
- 逻辑可以按功能聚合,复用更直观(
useXxx) - 对 TypeScript 更友好,类型更容易推断
Q7:为什么 Vue 3 的包更容易变小?
Vue 3 更偏模块化 + ESM,很多能力都可以被 Tree-Shaking 掉;而 Vue 2 更偏整体式设计,很多能力会被一起打包进来。
Q8:Vue 3 里为什么要用 createApp?它带来什么好处?
createApp() 让“应用级配置”绑定在 app 实例上,多个应用可以在同一页面共存且互不污染;同时也让插件、全局组件、指令的注册范围更明确,减少全局副作用。
Q9:Vue 2 项目迁移到 Vue 3,最常见的坑有哪些?
v-model行为变化(modelValue/update:modelValue)- 生命周期名称变化(destroy → unmount)
.native、filters 等特性移除- 第三方库版本不匹配(例如旧版 UI 库/路由/状态管理)
Q10:一句话解释“编译期优化”的价值
把“静态可知的信息”尽可能在编译时算清楚,让运行时用更少的分支、更少的遍历完成更新,从而更快。