Vue2和Vue3有什么区别?新项目该选哪个?
不少刚开始学Vue的朋友,或者要接手项目的开发者,总会纠结:Vue2和Vue3差别到底有多大?新项目选哪个更合适?老项目要不要急着迁移?今天咱们从核心原理、开发体验、生态兼容这些角度拆解,帮你选对方向。
核心原理差异:响应式、API设计、性能的迭代
响应式:从“Object.defineProperty”到“Proxy”的质变
Vue2靠Object.defineProperty实现响应式:遍历对象的每个属性,给属性加上getter
和setter
,拦截数据的读取和修改,但这种方式有明显缺陷:
- 对象新增/删除属性监听不到:比如给对象
obj
新增属性obj.newProp = 'xxx'
,Vue2没法检测到变化,页面不会更新,得用this.$set(obj, 'newProp', 'xxx')
手动触发。 - 数组特殊处理不彻底:Vue2对数组的
push/pop
等方法做了原型重写,但像arr[0] = 1
(直接改索引)、arr.length = 0
(改长度)这类操作,还是监听不到,得用this.$set(arr, 0, 1)
。
Vue3改用Proxy代理整个对象:通过拦截对象的get
(读取属性)、set
(修改属性)等行为,实现更全面的响应式,优势很直观:
- 对象任意操作都能监听:新增属性
obj.newProp = 'xxx'
、删除属性delete obj.oldProp
,Proxy都能检测到,不用手动调用$set/$delete
。 - 数组操作全拦截:不管是
arr[0] = 1
还是arr.length = 0
,Proxy都能捕获到变化,数组响应式终于“完整”了。 - 性能更优:Vue2要递归遍历对象的所有属性(哪怕深层属性暂时用不到),Proxy则是“懒拦截”,用到深层属性时再处理,初始化响应式数据的速度更快。
举个伪代码对比更直观:
Vue2响应式逻辑(简化版):
function observe(obj) { if (typeof obj !== 'object' || obj === null) return; Object.keys(obj).forEach(key => { let val = obj[key]; observe(val); // 递归处理深层对象 Object.defineProperty(obj, key, { get() { return val; }, set(newVal) { if (newVal !== val) { val = newVal; // 触发视图更新 } } }); }); }
Vue3响应式逻辑(简化版):
function reactive(obj) { return new Proxy(obj, { get(target, key) { // 收集依赖(比如哪个组件用了这个属性) track(target, key); return target[key]; }, set(target, key, newVal) { // 触发更新(通知依赖的组件重新渲染) trigger(target, key, newVal); return true; } }); }
代码组织:Options API到Composition API的逻辑革命
Vue2用Options API组织代码:把组件逻辑拆成data
、methods
、computed
、watch
等选项,简单组件写起来顺手,但复杂组件就暴露问题了——逻辑分散。
比如一个处理“用户信息加载+表单提交+定时器刷新”的组件,Vue2代码会变成这样:
export default { data() { return { user: {}, timer: null, form: { name: '' } } }, created() { this.fetchUser(); this.timer = setInterval(this.fetchUser, 5000) }, destroyed() { clearInterval(this.timer) }, methods: { async fetchUser() { this.user = await api.getUser() }, async submitForm() { await api.submit(this.form); this.fetchUser() } }, watch: { 'form.name'(newVal) { /* 验证逻辑 */ } } }
要找“用户信息相关逻辑”,得在data
、created
、destroyed
、methods
、watch
里跳来跳去,像拼碎片一样。
Vue3的Composition API(配合<script setup>
语法糖)则是按功能聚合逻辑,可以把同一功能的代码封装成“组合函数”,复用性和可维护性拉满,还是上面的例子,用Vue3改写:
// 抽离用户信息逻辑到 useUser.js import { ref, onMounted, onUnmounted } from 'vue'; import { api } from './api'; export function useUser() { const user = ref({}); const timer = ref(null); const fetchUser = async () => { user.value = await api.getUser(); }; onMounted(() => { fetchUser(); timer.value = setInterval(fetchUser, 5000); }); onUnmounted(() => { clearInterval(timer.value); }); return { user, fetchUser }; } // 抽离表单逻辑到 useForm.js import { ref, watch } from 'vue'; import { api } from './api'; export function useForm() { const form = ref({ name: '' }); const submitForm = async () => { await api.submit(form.value); }; watch(() => form.value.name, (newVal) => { // 名称验证逻辑 }); return { form, submitForm }; } // 组件中按需引入,逻辑清晰 <script setup> import { useUser } from './useUser.js'; import { useForm } from './useForm.js'; const { user, fetchUser } = useUser(); const { form, submitForm } = useForm(); // 提交后刷新用户信息 const handleSubmit = async () => { await submitForm(); fetchUser(); }; </script> <template> <div>{{ user.name }}</div> <input v-model="form.name" /> <button @click="handleSubmit">提交</button> </template>
可以看到,“用户信息”和“表单”的逻辑被分别封装到useUser
和useForm
里,组件只负责“组装”,后续维护时,找功能相关代码直接看对应的组合函数,不用在多个选项里翻找;如果其他组件需要类似逻辑,直接导入组合函数复用即可。
性能优化:编译和打包的双重升级
Vue3在编译阶段和打包阶段都做了优化:
-
编译优化(静态标记+PatchFlags):
Vue2的模板Diff是“全量对比”,不管节点是否变化,都要逐个比对,Vue3的编译器会给模板做静态标记:把节点分成“静态节点”(比如纯文本、不变的元素)和“动态节点”(比如带v-bind
、v-if
的元素),更新时,静态节点直接跳过,动态节点用PatchFlags标记更新类型(比如只更新文本、只更新class),实现“精准Diff”,减少不必要的计算。 -
打包优化(Tree-shaking):
Vue2的代码是“单例导出”,哪怕只用到一个API,也会打包整个库,Vue3改用“按需导出”,比如import { ref, computed } from 'vue'
,打包工具会自动剔除没用到的API(像Vue2里的filter
,Vue3已经移除,改用computed
或方法代替),最终Vue3核心库的体积比Vue2小了近一半,页面加载更快。