韦氏词典
48.02M · 2026-03-25
"Vue3 出来这么久了,到底要不要升级?"
这是很多前端团队都在纠结的问题。Vue2 项目跑得好好的,业务也稳定,为什么要花时间去升级?
答案是:性能 + 开发体验 + 未来支持。
Vue3 相比 Vue2,不仅仅是语法的改变,更是架构层面的全面优化:
今天,我们就来深入剖析 Vue3 相比 Vue2 的 8 大核心优化点,让你彻底搞懂升级的价值。
Vue2 使用 Object.defineProperty 实现响应式:
// Vue2 响应式原理(简化版)
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`获取 ${key}`);
return val;
},
set(newVal) {
console.log(`设置 ${key}`);
val = newVal;
// 通知更新
}
});
}
const data = { name: 'Vue2' };
defineReactive(data, 'name', 'Vue2');
// 问题 1:无法检测对象属性的添加和删除
data.age = 25; // 不会触发响应式更新
// 问题 2:无法检测数组索引和长度的变化
data.items[0] = 'new'; // 不会触发响应式更新
data.items.length = 0; // 不会触发响应式更新
// 解决方案:使用 Vue.set / this.$set
this.$set(data, 'age', 25);
this.$set(data.items, 0, 'new');
Vue3 使用 Proxy 重写响应式系统:
// Vue3 响应式原理(简化版)
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log(`获取 ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`设置 ${key}`);
return Reflect.set(target, key, value, receiver);
},
deleteProperty(target, key) {
console.log(`删除 ${key}`);
return Reflect.deleteProperty(target, key);
}
});
}
const data = reactive({ name: 'Vue3' });
// 优势 1:可以检测对象属性的添加和删除
data.age = 25; // 会触发响应式更新
delete data.name; // 会触发响应式更新
// 优势 2:可以检测数组索引和长度的变化
data.items[0] = 'new'; // 会触发响应式更新
data.items.length = 0; // 会触发响应式更新
// 优势 3:无需特殊 API,原生操作即可
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 对象属性添加 | 需要 Vue.set | 原生支持 |
| 数组索引修改 | 需要 Vue.set | 原生支持 |
| Map/Set 支持 | 不支持 | 原生支持 |
| 性能开销 | 较高(递归遍历) | 较低(懒代理) |
实测数据: 在大型列表中,Vue3 的响应式初始化速度比 Vue2 快 40-50%。
<!-- Vue2 Options API -->
<template>
<div>
<p>{{ userName }}</p>
<p>{{ userAge }}</p>
<button @click="fetchUser">加载用户</button>
</div>
</template>
<script>
export default {
data() {
return {
userName: '',
userAge: 0,
loading: false,
error: null
};
},
methods: {
async fetchUser() {
this.loading = true;
try {
const res = await fetch('/api/user');
const data = await res.json();
this.userName = data.name;
this.userAge = data.age;
} catch (e) {
this.error = e.message;
} finally {
this.loading = false;
}
}
},
computed: {
userTitle() {
return `${this.userName} - ${this.userAge}岁`;
}
},
watch: {
userName(newVal) {
console.log('用户名变化:', newVal);
}
},
mounted() {
this.fetchUser();
}
};
</script>
问题:
data、methods、computed、watch 分散在不同位置this 类型推断复杂<!-- Vue3 Composition API -->
<template>
<div>
<p>{{ userName }}</p>
<p>{{ userAge }}</p>
<p>{{ userTitle }}</p>
<button @click="fetchUser">加载用户</button>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
// 优势 1:逻辑聚合 - 相关代码在一起
const userName = ref('');
const userAge = ref(0);
const loading = ref(false);
const error = ref(null);
const userTitle = computed(() => `${userName.value} - ${userAge.value}岁`);
const fetchUser = async () => {
loading.value = true;
try {
const res = await fetch('/api/user');
const data = await res.json();
userName.value = data.name;
userAge.value = data.age;
} catch (e) {
error.value = e.message;
} finally {
loading.value = false;
}
};
//
watch(userName, (newVal) => {
console.log('用户名变化:', newVal);
});
// 生命周期
onMounted(() => {
fetchUser();
});
</script>
Vue2 Mixins(有问题):
// mixins/userLogic.js
export default {
data() {
return {
userName: '', // 命名冲突风险
loading: false
};
},
methods: {
fetchUser() {} // 来源不清晰
}
};
// 组件中
export default {
mixins: [userLogic, otherMixin], // 多个 mixins 冲突怎么办?
};
Vue3 Composables(优雅):
// composables/useUser.js
import { ref } from 'vue';
export function useUser() {
const userName = ref('');
const loading = ref(false);
const fetchUser = async () => {
// ...
};
return { userName, loading, fetchUser }; // 清晰明确
}
// 组件中
import { useUser } from '@/composables/useUser';
const { userName, loading, fetchUser } = useUser(); // 无冲突
| 框架 | 最小 + 压缩体积 | 相比 Vue2 |
|---|---|---|
| Vue2 | ~30 KB | - |
| Vue3 | ~10 KB | 减少 41% |
原因:
// Vue3 按需引入
import { ref, computed, watch } from 'vue'; // 只引入需要的
// Vue2 全量引入
import Vue from 'vue'; // 全部引入
| 场景 | Vue2 | Vue3 | 提升 |
|---|---|---|---|
| 初次渲染 | 基准 | 快 40-50% | ⬆️ 45% |
| 更新渲染 | 基准 | 快 40-50% | ⬆️ 45% |
| 内存占用 | 基准 | 减少 50% | ⬇️ 50% |
原因:
<!-- Vue3 编译优化 -->
<template>
<div>
<p>静态文本</p> <!-- 静态节点,不追踪 -->
<p>{{ dynamicText }}</p> <!-- 动态节点,带 PatchFlags -->
</div>
</template>
<!-- 编译后(简化) -->
{
type: 'div',
children: [
{ type: 'p', children: '静态文本', patchFlag: 0 }, // 静态
{ type: 'p', children: dynamicText, patchFlag: 1 } // 动态,只追踪文本
]
}
// Vue2 + TypeScript(繁琐)
import Vue from 'vue';
import Component from 'vue-class-component';
@Component({
props: {
userId: Number,
userName: String
}
})
export default class UserCard extends Vue {
// 需要装饰器
// 类型推断复杂
// 配置繁琐
get userTitle() {
return `${this.userName} - ${this.userId}`;
}
}
// Vue3 + TypeScript(原生)
<script setup lang="ts">
import { ref, defineProps, defineEmits } from 'vue';
// 原生 TypeScript 支持
interface Props {
userId: number;
userName: string;
}
const props = defineProps<Props>();
// 自动类型推断
const userTitle = `${props.userName} - ${props.userId}`;
// 事件类型定义
const emit = defineEmits<{
(e: 'update', id: number): void;
(e: 'delete'): void;
}>();
</script>
优势:
| Vue2 生命周期 | Vue3 生命周期 | 说明 |
|---|---|---|
| beforeCreate | setup() | 在 setup 中直接写 |
| created | setup() | 在 setup 中直接写 |
| beforeMount | onBeforeMount | 类似 |
| mounted | onMounted | 类似 |
| beforeUpdate | onBeforeUpdate | 类似 |
| updated | onUpdated | 类似 |
| beforeDestroy | onBeforeUnmount | 改名了 |
| destroyed | onUnmounted | 改名了 |
Vue2:
export default {
data() {
return { count: 0 };
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeDestroy() {
console.log('beforeDestroy');
},
destroyed() {
console.log('destroyed');
}
};
Vue3:
import { onBeforeMount, onMounted, onBeforeUnmount, onUnmounted } from 'vue';
setup() {
onBeforeMount(() => {
console.log('onBeforeMount');
});
onMounted(() => {
console.log('onMounted');
});
onBeforeUnmount(() => {
console.log('onBeforeUnmount');
});
onUnmounted(() => {
console.log('onUnmounted');
});
};
优势:
<!-- Vue2:模态框被父组件样式影响 -->
<template>
<div class="modal-container">
<div class="modal" v-if="show">
<!-- 受父组件 overflow: hidden 影响 -->
<!-- 受父组件 z-index 影响 -->
模态框内容
</div>
</div>
</template>
<style>
.modal-container {
overflow: hidden; /* 模态框被裁剪 */
}
</style>
<!-- Vue3:传送到 body 下 -->
<template>
<Teleport to="body">
<div class="modal" v-if="show">
<!-- 不受父组件样式影响 -->
<!-- 始终在最上层 -->
模态框内容
</div>
</Teleport>
</template>
优势:
<!-- Vue2:需要手动处理 loading 状态 -->
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-else-if="error">加载失败</div>
<AsyncComponent v-else />
</div>
</template>
<script>
export default {
components: {
AsyncComponent: () => ({
component: import('./AsyncComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
})
},
data() {
return {
loading: true,
error: null
};
}
};
</script>
<!-- Vue3:内置异步处理 -->
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import AsyncComponent from './AsyncComponent.vue';
// 自动处理 loading 和 error 状态
</script>
优势:
<!-- Vue2:必须有一个根节点 -->
<template>
<div> <!-- 多余的 div -->
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
<!-- Vue3:支持多个根节点 -->
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
<!-- 无需多余的包裹 div -->
<!-- 更简洁的 DOM 结构 -->
| 优化点 | Vue2 | Vue3 | 提升幅度 |
|---|---|---|---|
| 打包体积 | ~30 KB | ~10 KB | ⬇️ 41% |
| 渲染速度 | 基准 | 快 40-50% | ⬆️ 45% |
| 内存占用 | 基准 | 减少 50% | ⬇️ 50% |
| TypeScript 支持 | 一般 | 优秀 | 质的飞跃 |
| 代码复用 | Mixins(有问题) | Composables | 架构升级 |
| 响应式原理 | Object.defineProperty | Proxy | 原生支持 |
@vue/compat 兼容版本Vue3 相比 Vue2,不仅仅是版本升级,更是架构层面的全面优化:
最重要的建议:
Vue2 已经停止维护,未来是 Vue3 的时代。早升级,早受益!
如果觉得这篇文章对你有帮助,欢迎点攒、收藏、关注三连支持!
你的项目升级 Vue3 了吗?遇到过什么坑?欢迎在评论区分享!
本文首发于掘金,欢迎交流讨论