一文吃透 Vue2 与 Vue3 的核心差异
1. API 设计风格:从 Options API 到 Composition API
Vue 2 (Options API):我们习惯于将组件的逻辑分散在 data、methods、computed、watch 等不同的选项中。这种方式对于中小型组件很直观,但在大型复杂组件中,一个功能的逻辑(比如数据、方法、计算属性)会被拆分到代码的各个角落,导致逻辑碎片化,后期维护和代码复用非常困难。
Vue 3 (Composition API):它允许我们根据逻辑功能来组织代码。所有与某个特定功能相关的响应式状态、方法、计算属性等,都可以放在同一个 setup 函数中。
Composition API可以非常轻松地将这些逻辑抽离成可复用的组合式函数 (Composables)。这彻底解决了 Vue 2 中 Mixin 模式带来的问题,比如命名冲突、数据来源不清晰等。
2. 底层响应式原理:从 Object.defineProperty 到 Proxy
Vue 2:它使用 Object.defineProperty 来劫持对象属性的 getter 和 setter。它是属性接别的劫持,这种方式存在一些天生缺陷:
- 无法监听对象属性的新增和删除。必须使用 Vue.set 这种略显笨拙的 API 来解决。
- 无法直接监听数组索引的变化和长度的变化。Vue 2 通过重写数组的部分方法来实现监听,但依然有局限性。
Vue 3:它改用了 ES6 的 Proxy 对象。Proxy 可以直接代理整个对象,而不是单个属性。
- 原生支持所有监听:无论是新增/删除属性,还是直接通过索引修改数组,Proxy 都能监听到,不再需要 this.$set。
- 性能更优:Object.defineProperty 需要在初始化时就遍历对象的所有属性进行劫持,而 Proxy 是惰性的,只有在访问属性时才会进行处理,性能开销更小。
3. 性能提升
Vue 2 整体打包,Tree-Shaking 效果差,Vue 3 源码使用 ES Module 重构,支持 Tree-Shaking,用多少打多少,体积更小。
Vue 2 基于 VNode diff,但是性能上有些限制, Vue 3 引入 静态标记(Patch Flag) 和区块树 (Block Tree),只更新有变化的部分,性能大幅提升。
3.1. 什么是静态标记与区块树?
区块树是告诉哪里需要更新,静态标记是告诉怎么更新(比如说,当一个 p 标签从 1 变成了 2,那么它就会像一个备注一样,告诉这个应该如何更新),
区块树就是那份“需要更新的动态节点的清单”。
再编译的时候,Vue3 会把所有的静态节点扔到一边(静态树提升),然后把所有动态节点收集起来,生成一个扁平的数组,这个只包含动态节点的就是数组,就是区块树。
当组件需要更新的时候,Vue2 必须遍历完整的全部的 DOM 节点,而 Vue3 只需要遍历这个扁平的,短更多的“区块树”即可。
其实简单来说就是这样的:
- 对于静态 DOM:Vue 3 通过静态树提升,在编译时就将其提升为常量。在更新时,直接跳过,完全不进行遍历和 Diff。
- 对于动态 DOM:
-
- Vue 3 会将它们收集到一个区块树 (Block Tree) 中,这是一个只包含动态节点的扁平数组。
- 同时,会给区块树中的每一个动态节点都打上一个静态标记 (Patch Flag),明确告诉渲染器这个节点只需要更新哪一部分(文本、class、style 等)。
- 最终效果:
-
- 当需要更新时,Vue 2 必须遍历完整的虚拟 DOM 树。
- 而 Vue 3 只需要遍历短小精悍的区块树,并且在更新每个节点时,还会参考静态标记,只做最必要的操作
4. 更好的 TypeScript
Vue 3 是用 TypeScript 重写的,它的类型定义更加完善和强大,结合 Composition API,可以提供非常出色的类型推断和开发体验,这对于构建大型、健壮的应用至关重要。
5. Fragments(多根节点)
Fragment (片段):Vue 3 的组件可以有多个根节点,不再需要一个无意义的 <div> 包裹。
6. Teleport(传送门)
- Vue2:没有,需要手写。
- Vue3:提供
<teleport>,可将 DOM 结构渲染到指定位置(常用于弹窗)。
7. 生命周期
|
Vue 2 (Options API) |
Vue 3 (Composition API) |
说明 |
|
beforeCreate |
setup() |
setup 是所有 Composition API 的入口,它在组件实例创建前执行,因此完全取代了 beforeCreate。 |
|
created |
setup() |
setup 函数中定义的响应式数据、方法等都在 created 阶段之前完成,所以它也取代了 created。 |
|
beforeMount |
onBeforeMount |
组件 DOM 即将被挂载到页面之前执行。 |
|
mounted |
onMounted |
组件 DOM 已经被挂载到页面之后执行。 |
|
beforeUpdate |
onBeforeUpdate |
组件因数据变化即将更新其 DOM 树之前执行。 |
|
updated |
onUpdated |
组件因数据变化更新完其 DOM 树之后执行。 |
|
beforeDestroy |
onBeforeUnmount |
组件实例即将被卸载之前执行。名称变化更准确。 |
|
destroyed |
onUnmounted |
组件实例被卸载之后执行。 |
|
errorCaptured |
onErrorCaptured |
捕获子孙组件的错误时执行。 |
|
activated |
onActivated |
被 <keep-alive> 缓存的组件被激活时执行。 |
|
deactivated |
onDeactivated |
被 <keep-alive> 缓存的组件失活时执行。 |
7.1. setup 函数取代了 beforeCreate 和 created
这是最关键的一个变化。在 Vue 2 中,我们在 created 钩子中初始化数据、发起网络请求等。但在 Vue 3 的 Composition API 中,这些工作都前置到了 setup 函数里。因为 setup 是组件初始化的入口,所有响应式状态、方法和计算属性都在这里创建,所以它天然地覆盖了 beforeCreate 和 created 的所有功能。
7.2. 命名和使用方式的统一
- onX 命名规范:所有 Vue 3 的生命周期钩子都以 on 开头,比如 onMounted。这使得 API 更加见名知意,一眼就能看出这是一个注册生命周期回调的函数。
- 需要显式导入:在 <script setup> 中,你需要从 'vue' 中显式导入这些钩子函数才能使用,比如 import { onMounted } from 'vue'。这让代码的依赖关系更加清晰。
7.3. 逻辑组合的灵活性 (最重要的优点)
在 Vue 2 的 Options API 中,如果你有一段逻辑需要在 created 中初始化,在 mounted 中绑定事件,在 beforeDestroy 中清理事件,那么这段逻辑的代码会分散在三个不同的生命周期选项里。
而在 Vue 3 中,你可以把这些都写在一起,围绕着某个具体的功能来组织代码。
import { ref, onMounted, onUnmounted } from 'vue';
// 这就是一个组合式函数
export function useMouse() {
// 1. 状态 (原本在 data 中)
const x = ref(0);
const y = ref(0);
// 2. 功能逻辑 (原本在 methods 中)
const update = (event) => {
x.value = event.pageX;
y.value = event.pageY;
};
// 3. 生命周期钩子 (原本在 mounted 和 beforeDestroy 中)
onMounted(() => {
window.addEventListener('mousemove', update);
});
onUnmounted(() => {
window.removeEventListener('mousemove', update);
});
// 4. 把需要的数据和方法暴露出去
return { x, y };
}
<template>
<div>Mouse position is: {{ x }}, {{ y }}</div>
</template>
<script setup>
import { useMouse } from './useMouse.js'; // 1. 导入
const { x, y } = useMouse(); // 2. 调用,并解构出需要的数据
</script>
更多推荐


所有评论(0)