一文带你深入理解vue2与vue3的差异(超详细)
Vue3相较于Vue2的主要改进:1. 响应式系统升级:Vue3采用Proxy替代Object.defineProperty,解决Vue2无法检测动态属性/数组长度变化等问题;2. 组合式API:提供setup()和ref/reactive等函数,按功能组织代码,优于Vue2的OptionsAPI;3. 新特性支持:包括多根节点(Fragment)、Teleport传送门、Suspense异步组件
一.响应式系统
vue2:
-
vue2的响应式系统基于object.defineProperty()实现,通过劫持对象属性的getter/setter来追踪依赖并触发更新。
-
vue2无法检测对象属性的动态添加或删除,因为object.defineProperty()必须在对象属性存在时才能劫持。
export default{
data(){
return{
user:{name:'Tom'}
}
},
created(){
//无法触发响应式更新
this.user.age = 18 //新属性没有经过 Object.defineProperty() 处理,因此 Vue 无法追踪它的变化。
//正确方式,使用vue.set,它内部实现了:1.使用 Object.defineProperty() 为新属性创建 getter/setter;2.触发依赖更新
this.$set(this.user,'age',18)
}
}
-
vue2无法检测通过修改数组长度(如arr.length=0) 触发的变化(因为vue2的响应式系统不直接劫持数组的索引,也就是没有为数组的每个索引如arr[0]、arr[1]设置getter、setter,只能通过重写数组的变异方法(如
push、pop、splice等)来劫持数组的变化。) -
length属性的修改属于 非变异操作,Vue2 没有对其进行特殊处理。因此,当你直接修改数组的length属性时,Vue2 无法捕获到这个变化。
export default {
data() {
return {
items: [1, 2, 3]
}
},
created() {
// 无法触发响应式更新
this.items.length = 0;
// 正确方式:使用 splice,替代直接修改length
this.items.splice(0);
}
}
vue3:
vue3的响应式系统基于ES6 Proxy实现,相比于vue2的object.defineProperty()有了根本性的改进。它解决了vue2中存在的诸多限制,比如Vue3无需特殊API,直接添加属性即可触发响应式更新(user.age = 30)、可以直接检测length的变化(item.length = 0),可以检测对象原型的修改等。
vue3使用proxy对象拦截对象的所有操作(读取、设置、删除等),从而实现对数据的全面监控。与vue2的defineProperty()相比,Proxy的优势在于:
-
拦截范围更广:可以拦截对象属性的添加、删除、枚举、函数调用等操作;
-
无需递归遍历:可以直接监听整个对象,而不需要递归处理所有嵌套属性,只有在访问嵌套对象时才会创建
Proxy; -
按需响应式:只有访问到的嵌套对象才会被转换为响应式;
总结:
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 实现原理 | 基于 Object.defineProperty () | 基于 ES6 Proxy |
| 深层响应式 | 需要递归遍历所有嵌套对象 | 原生支持深层响应式,无需特殊处理 |
| 动态属性添加 | 需使用 Vue.set () 或 this.$set () | 直接添加属性即可触发响应式更新 |
| 数组变异方法 | 需使用特定变异方法(如 push/pop) | 无限制,Proxy 可拦截所有操作 |
| 局限性 | 无法检测对象属性的删除、新增,数组长度变化 | 无上述限制,提供更一致的响应式行为 |
二.组合式 API vs Options API
vue2(options API)
- 问题:逻辑分散在不同选项(data/methods/computed),大型项目难以维护
//vue2 Options API
export default{
data(){
return{
count:0,
loading:false,
error:null
}
},
computed:{
addCount(){
return this.count*2;
}
} ,
methods:{
async fetchData(){
this.loading = true;
try{
//fetch()是浏览器原生 API 用于向指定 URL 发送 HTTP 请求,并返回一个 Promise 对象
//也可使用主流的axios HTTP客户端
const res = await fetch('/api/data')
this.data = await res.json();// 解析响应为 JSON
}catch(e){
this.error = e;
}finally{
this.loading = false;
}
}
}
}
vue3(组合式API)
- 优势:按功能组织代码,逻辑内聚,提高复用性
//vue3(组合式API)
import {ref,computed,onMounted } from 'vue';
export default{
setup{ //可使用 <script setup> 语法糖,代码更简洁
//状态管理
const count = ref(0);
const addCount = computed(()=>count.value*2)
//副作用逻辑
const loading = ref(false);
const error = ref(null);
const data = ref(null);
const fetchData = async() => {
loading.value = true;
try{
const res = await fetch('/api/data');
data.value = await res.json();
}catch(e){
error.value = e;
}finally{
loading.value = false;
}
}
onMounted(()=>{
fetchData();
});
return {count,doubleCount,loading, error,data,fetchData};
}
}
总结:
| 特性 | Vue2 | Vue3 |
|---|---|---|
| API 风格 | Options API(data/methods/computed) | 组合式 API(setup ()、ref/reactive) |
| 逻辑复用 | 混入(Mixins)、高阶组件(HOC) | 组合函数(Composition Functions) |
| 代码组织 | 按选项拆分,逻辑分散 | 按功能组织,逻辑内聚 |
| 类型支持 | TypeScript 支持有限 | 原生支持 TypeScript,类型推导更完善 |
三.生命周期钩子
| Vue2 钩子 | Vue3 等效钩子 | 说明 |
|---|---|---|
| beforeCreate () { } | setup() | 组件初始化前执行 |
| created () { } | setup() | 组件实例创建后执行 |
| beforeMount () { } | onBeforeMount (()=>{}) | DOM 挂载前执行 |
| mounted () { } | onMounted (()=>{}) | DOM 挂载后执行 |
| beforeUpdate () { } | onBeforeUpdate (()=>{}) | 数据更新前执行 |
| updated () { } | onUpdated (()=>{}) | 数据更新后执行 |
| beforeDestroy () { } | onBeforeUnmount (()=>{}) | 组件销毁前执行 |
| destroyed () { } | onUnmounted (()=>{}) | 组件销毁后执行 |
| activated () { } | onActivated (()=>{}) | 缓存组件激活时执行 |
| deactivated () { } | onDeactivated (()=>{}) | 缓存组件停用时执行 |
| errorCaptured () { } | onErrorCaptured (()=>{}) | 捕获子组件错误 |
四.组件与模版
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 多根节点(Fragment) | 不支持,组件必须有单个根元素 | 支持多根节点,无需包裹元素 |
| Teleport | 无 | 支持 <teleport> 组件,可将内容渲染到 DOM 任意位置 |
| Suspense | 无 | 支持 <Suspense> 组件,处理异步依赖 |
| 自定义指令 | 基于对象的 API | 支持组合式 API,使用 setup () |
-
多根节点:
//vue2必须有单个根元素
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
//vue3 支持多根节点 可以有多个div并行
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
-
teleport组件(传送门) vue3独有
作用:将组件内容渲染到 DOM 中的其他位置,而不必改变组件的逻辑结构。常见场景:模态框、悬浮提示、全屏组件等。
<template>
<div>
<button @click="showModal = true">Open Modal</button>
<teleport to="body">
<div v-if="showModal" class="modal">
<p>Modal Content</p>
<button @click="showModal = false">Close</button>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue';
const showModal = ref(false);
</script>
-
Suspense 组件(异步组件处理)vue3独有
作用:处理异步组件和异步数据加载的状态管理,提供加载中、加载失败等过渡状态。
<template>
<Suspense>
<!-- 加载完成后显示 -->
<template #default>
<AsyncComponent />
</template>
<!-- 加载中显示 -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
);
export default {
components: { AsyncComponent }
}
</script>
五.性能优化
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 编译优化 | 全量更新,虚拟 DOM 比对开销大 | 基于动态标记(PatchFlag)的靶向更新,仅更新变化部分 |
| Tree-shaking | 不支持,所有功能打包进应用 | 支持按需引入,体积更小 |
| 渲染函数 | 性能一般 | 优化的渲染函数,执行效率更高 |
| 内存占用 | 较高,每个实例维护多个 watcher | 降低约 40% 内存占用 |
六.Typescript支持
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 类型支持 | 需要额外插件(vue-class-component) | 原生支持,无需额外配置 |
| 类型推导 | 有限,需要手动声明类型 | 基于组合式 API 的自动类型推导 |
| DefineComponent | 无 | 提供类型增强的组件定义函数 |
示例:
<template>
<div>
<p>{{ message }}</p>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, defineProps, defineEmits } from 'vue';
//定义props类型
const props = defineProps<{
message:string;
initialCount?:number;
}>()
//定义emits类型
const emits = defineEmits<{
( event:'change',value:number ):void;
}>();
//响应式数据带类型
const count = ref<number>(props.initialCount||0);
//计算属性
const doubleCount = computed(()=> count.value*2)
//方法
const increment = ()=>{
count.value++;
emits('change',count.value);
}
</script>
七.其他重要特性
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 全局 API | 全局配置(Vue.config) | 创建应用实例(createApp),避免全局污染 |
| v-model | 单个 v-model 绑定 | 支持多个 v-model 绑定,自定义修饰符 |
| Composition API | 无 | 提供 setup ()、provide/inject、customRef 等 |
| 生态系统 | 成熟但部分库需适配 | 新库(如 Vue Router 4、Pinia)优先支持 Vue3 |
-
全局API
vue2:
-
全局构造函数:使用vue构造函数创建应用实例,所有配置直接挂载到全局vue对象上;
-
全局污染:任何组件都可以访问和修改全局配置,导致潜在的冲突;
//vue2
import Vue from 'vue';
import App from './App.vue' ;
import VueRouter from 'vue-router';
import MyButton from './components/MyButton.vue';
// 全局安装插件
Vue.use(VueRouter);
// 创建路由实例
const router = new VueRouter({...});
// 全局注册组件
Vue.component('MyButton', MyButton);
//全局配置(影响所有实例)
Vue.config.productionTip = false;
Vue.prototype.$http = axios; //挂载全局属性
//创建根实例,在根实例中使用路由
new Vue({
router,
render: h => h(App)
}).$mount('#app')
vue3:
-
应用实例隔离:使用createApp创建独立的应用实例,所有配置仅作用于当前实例。
-
避免全局污染:每个应用实例有自己的配置,不同应用可共存且互不影响。
//Vue3
import {createApp} from 'vue';
import App from './App.vue';
import router from './router';
import MyButton from './components/MyButton.vue';
//创建应用实例
const app = createApp(App);
// 全局注册组件
app.component('MyButton', MyButton);
// 实例级安装插件
app.use(router);
//实例配置(仅影响当前应用)
app.config.productionTip = false;
app.provide('$http',axios);//提供依赖注入,替代vue.prototype
//挂载应用
app.mount('#app');
V-model:
Vue2 和 Vue3 在 v-model 指令上的实现和功能有显著差异,主要体现在 语法糖本质、组件间通信方式、多 v-model 支持 和 修饰符机制 等方面。
vue2:
- 本质:v-model是语法糖,在原生元素和组件中有不同表现;
-
原生元素:绑定value属性并监听input事件
-
组件:绑定
value属性并监听input事件。
-
//vue2原生元素
<input v-model = 'message'>
//等价于
<input :value = 'message' @input="message=$event.target.value">
//vue2组件
<CustomInput v-model="message">
<!-- 等价于 -->
<CustomInput :value="message" @input="message = $event">
vue3:
-
本质:
v-model是 语法糖,但默认绑定modelValue属性并监听update:modelValue事件,更灵活且支持多 v-model。-
原生元素:行为与 vue2 一致(绑定
value和input)。 -
组件:绑定
modelValue属性并监听update:modelValue事件。
-
<!-- Vue3:原生元素 -->
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
<!-- Vue3:组件 -->
<CustomInput v-model="message">
<!-- 等价于 -->
<CustomInput :modelValue="message" @update:modelValue="message = $event">
多 v-model 绑定(Vue3 新增)
Vue3 支持在同一组件上使用 多个 v-model 绑定,每个绑定对应不同的 prop 和事件。
<!-- 父组件 -->
<CustomForm
v-model:username="username"
v-model:password="password"
/>
<!-- 等价于 -->
<CustomForm
:username="username"
@update:username="username = $event"
:password="password"
@update:password="password = $event"
/>
//子组件:CustomForm.vue
<template>
<div>
<input
:value="username"
@input="$emit('update:username', $event.target.value)"
>
<input
:value="password"
@input="$emit('update:password', $event.target.value)"
>
</div>
</template>
<script>
export default {
props: {
username: String,
password: String
},
emits: ['update:username', 'update:password']
}
</script>
修饰符对比:
| 修饰符 | Vue2 | Vue3 | 说明 |
|---|---|---|---|
.trim |
✅ | ✅ | 自动过滤输入首尾空格 |
.number |
✅ | ✅ | 将输入转换为数字类型 |
.lazy |
✅ | ✅ | 监听 change 事件而非 input |
.debounce |
❌ | ❌ | Vue2/3 均不内置,需自定义 |
| 自定义修饰符 | 需插件 | ✅ | Vue3 原生支持组件级自定义修饰符 |
总结:
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 默认 prop / 事件 | value / input |
modelValue / update:modelValue |
| 多 v-model 支持 | 不支持 | 支持 |
| 自定义修饰符 | 需插件实现 | 原生支持,通过 modelModifiers |
| emits 选项 | 不强制 | 推荐声明,增强类型安全 |
| 与表单元素绑定 | 直接绑定 value |
组件中需绑定 modelValue |
Tree-shaking支持:
vue3实现了更好的tree-shaking支持,核心库被拆分成多个包,使得打包工具可以移除未使用的代码,减小应用体积。
在vue2中,即使没有使用某些功能(如v-model、过渡动画),它们也会被打包到最终的构建中。而Vue3的API大多数都是从vue包中单独引入的函数,如果没有使用就不会被打包。
总结:
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 响应式原理 | Object.defineProperty() | Proxy |
| API 风格 | Options API | 组合式 API |
| 多根节点 | 不支持 | 支持 |
| Teleport/Suspense | 无 | 内置组件 |
| 全局 API | 全局污染 | 应用实例隔离 |
| v-model | 单个绑定 | 多个绑定 + 自定义修饰符 |
| TypeScript 支持 | 需额外插件 | 原生支持 |
| 逻辑复用 | Mixins(命名冲突、类型问题) | 组合函数(灵活、类型安全) |
| 性能 | 全量虚拟 DOM 比对 | PatchFlag 靶向更新 |
面试题:Vue2与Vue3有哪些区别?
回答重点:(1)性能提升(响应式系统)(2)组合式API (3)Teleport组件 (4)片段(Fragment)多根节点 (5)Typescript支持(6)Suspence组件 (7)Tree-shaking支持
更多推荐



所有评论(0)