ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue2与Vue3核心区别全解析:从响应式原理到迁移实战

Vue2与Vue3核心区别全解析:从响应式原理到迁移实战 1. 从一次真实迁移说起为什么我要把 Vue2 和 Vue3 的区别彻底捋一遍去年接手了一个后台管理项目代码是 2020 年用 Vue2 Element UI 写的业务逻辑堆了三年组件两百多个。产品那边要求加一套数据看板需要用到组合式逻辑复用、更好的 TypeScript 支持还有打包体积优化。我第一反应是能不能不升级毕竟 Vue2 跑得好好的。但评估下来发现新看板里大量图表联动、状态共享、动态表单用 Vue2 的 mixin 和 options API 写下去维护成本会爆炸。于是硬着头皮做了一次渐进式迁移踩了大概两周的坑才把 Vue2 和 Vue3 的差异真正吃透。这篇内容就是那次迁移的完整总结。我会从响应式原理、API 风格、生命周期、模板语法、组件通信、生态工具、性能表现、迁移策略这几个维度把 Vue2 和 Vue3 的区别讲清楚。不管你是刚学 Vue 的新手还是手里攥着老项目准备升级的老手都能从里面找到能直接抄作业的部分。核心关键词就两个vue2和vue3全文围绕它们的对比展开不跑题。先说结论Vue3 不是 Vue2 的简单升级它在底层做了一次重构API 风格也换了一套思路。理解这个前提后面的差异就都顺了。2. 响应式系统从 Object.defineProperty 到 Proxy 的底层换血2.1 Vue2 的响应式是怎么实现的Vue2 用的是Object.defineProperty来劫持对象的属性读写。具体做法是在初始化数据时遍历 data 里的每个属性用defineProperty给它们加上 getter 和 setter。getter 里收集依赖也就是谁用了这个数据setter 里通知依赖更新。这套机制能跑但有几个硬伤无法监听新增和删除属性。你给对象加一个新 keyVue2 是感知不到的必须用Vue.set或this.$set手动触发。无法监听数组索引和 length 的变化。arr[0] x这种操作Vue2 监听不到所以它重写了数组的七个方法push、pop、shift、unshift、splice、sort、reverse来打补丁。初始化时递归遍历所有属性。数据层级深、对象大的时候初始化性能会明显下降。我当年写 Vue2 的时候最烦的就是this.$set一个不小心忘了加页面就不更新排查半天。2.2 Vue3 的 Proxy 方案强在哪Vue3 换成了Proxy代理整个对象配合Reflect做操作转发。Proxy 的好处是能拦截整个对象的操作包括新增、删除、in判断、delete等不需要$set了。懒代理。Vue3 只在真正访问到某个嵌套属性时才递归代理它初始化性能更好。数组原生支持。不需要重写数组方法arr[0] x也能触发更新。不过要注意Proxy 对 IE 完全不支持这也是 Vue3 放弃 IE11 的根本原因。如果你的项目还要兼容 IE那 Vue3 直接出局别纠结。2.3 一个对比表格看清楚对比项Vue2Vue3实现方式Object.definePropertyProxy Reflect新增/删除属性需 $set / $delete自动监听数组索引修改不支持支持初始化性能全量递归较慢懒代理较快IE 兼容支持 IE11不支持嵌套对象递归劫持按需代理提示Vue3 的reactive只能代理对象类型基本类型要用ref。这是新手最容易搞混的点后面会细讲。3. API 风格之争Options API 与 Composition API 的取舍3.1 Vue2 的 Options API 长什么样Vue2 的组件就是一个配置对象data、methods、computed、watch、生命周期钩子各占一块。写小页面很舒服逻辑按类型分块一眼能看清。export default { data() { return { count: 0, list: [] } }, computed: { double() { return this.count * 2 } }, methods: { add() { this.count } }, mounted() { this.fetchList() } }问题出在组件变大之后。一个功能相关的逻辑被拆散到 data、methods、computed、mounted 好几个地方。你想改一个搜索功能得在文件里上下横跳。这就是所谓的逻辑关注点分离问题。3.2 Vue3 的 Composition API 怎么解决Composition API 允许你把同一个功能的逻辑写在一起用setup或者script setup组织。import { ref, computed, onMounted } from vue export default { setup() { const count ref(0) const double computed(() count.value * 2) const add () count.value onMounted(() { /* fetch */ }) return { count, double, add } } }更推荐的是script setup语法糖代码量能再砍一半script setup import { ref, computed, onMounted } from vue const count ref(0) const double computed(() count.value * 2) const add () count.value onMounted(() { /* fetch */ }) /script3.3 两种风格怎么选我的经验是新项目一律用 Composition API老项目渐进迁移。Composition API 在逻辑复用、TypeScript 类型推导、代码组织上全面占优。但 Options API 在 Vue3 里依然可用小页面写起来反而更简洁。维度Options APIComposition API逻辑组织按类型分块按功能聚合逻辑复用mixin易冲突组合式函数清晰TS 支持较弱强学习曲线平缓稍陡适用场景简单组件复杂组件、大型项目注意mixin 的命名冲突和来源不清晰是老大难问题Composition API 的自定义 hook比如useCounter彻底解决了这个痛点。4. 生命周期钩子的变化改名、合并与新增4.1 钩子名称对照Vue3 把大部分生命周期钩子前面加了on并且beforeDestroy和destroyed改成了beforeUnmount和unmounted。这个改名不是随便改的是为了和unmounted的语义对齐——组件卸载而不是销毁。Vue2Vue3OptionsVue3CompositionbeforeCreatebeforeCreatesetupcreatedcreatedsetupbeforeMountbeforeMountonBeforeMountmountedmountedonMountedbeforeUpdatebeforeUpdateonBeforeUpdateupdatedupdatedonUpdatedbeforeDestroybeforeUnmountonBeforeUnmountdestroyedunmountedonUnmountedactivatedactivatedonActivateddeactivateddeactivatedonDeactivatederrorCapturederrorCapturedonErrorCaptured4.2 setup 替代了 beforeCreate 和 created在 Composition API 里setup执行时机就在beforeCreate和created之间所以这两个钩子不需要单独写了逻辑直接放setup里。这一点很多人第一次迁移会懵以为钩子丢了。4.3 新增的调试钩子Vue3 加了onRenderTracked和onRenderTriggered用来调试响应式依赖的收集和触发。开发环境下用它们能快速定位为什么这个组件重新渲染了。onRenderTriggered((event) { console.log(触发渲染的依赖, event) })我实测下来排查性能问题时这两个钩子比 Vue Devtools 还直接。5. 模板语法与指令的细节差异5.1 多根节点Fragment 的引入Vue2 的模板必须有一个根元素写多个根节点直接报错。Vue3 支持多根节点底层用 Fragment 包裹不需要再套一个无意义的 div。template header标题/header main内容/main footer底部/footer /template这个改动看着小但实际写布局时省了很多嵌套DOM 结构也更干净。5.2 v-model 的升级Vue2 里一个组件只能有一个v-model多个双向绑定要用.sync。Vue3 把.sync干掉了v-model可以带参数一个组件能写多个。!-- Vue2 -- Child v-modelvalue :title.synctitle / !-- Vue3 -- Child v-modelvalue v-model:titletitle /而且 Vue3 的v-model默认 prop 从value改成了modelValue事件从input改成了update:modelValue。自定义组件时这个必须改否则双向绑定失效。5.3 v-if 与 v-for 的优先级Vue2 里v-for优先级高于v-if意味着v-if会在每次循环里判断一次性能差。Vue3 反过来了v-if优先级高于v-forv-if先执行v-for里的变量在v-if中访问不到。!-- Vue3 里这样写会报错因为 item 还没定义 -- li v-foritem in list v-ifitem.active{{ item.name }}/li正确做法是用 computed 过滤或者把v-if提到外层。5.4 key 的位置变化Vue3 里key要写在template标签上而不是子元素上。!-- Vue3 -- template v-foritem in list :keyitem.id li{{ item.name }}/li /template5.5 事件修饰符和按键修饰符Vue3 移除了v-on的.native修饰符因为组件根元素的事件默认就会透传。按键修饰符也不再支持自定义别名配置要用keyup.enter这种内置的或者自己判断event.key。6. 组件通信方式的演进6.1 props 和 emitVue3 的emit需要在setup里显式声明或者用defineEmits。script setup const emit defineEmits([update, delete]) emit(update, payload) /scriptVue2 里this.$emit随便用Vue3 里不声明会有警告但功能上还能跑。建议还是声明类型提示更友好。6.2 透传属性 attrsVue3 的$attrs包含了 class 和 styleVue2 里这两个是排除在外的。而且 Vue3 支持inheritAttrs: false配合v-bind$attrs手动控制透传位置这个在封装基础组件时特别有用。6.3 provide / injectVue3 的provide和inject可以在setup里调用而且 inject 支持默认值和响应式。// 父组件 provide(theme, ref(dark)) // 子组件 const theme inject(theme, ref(light))Vue2 的 provide/inject 默认不是响应式的要传对象才行。Vue3 里传 ref 就是响应式的这个改进很实用。6.4 全局 API 的变化Vue2 用Vue.prototype.$http axios挂全局方法Vue3 改成app.config.globalProperties.$http axios。而且 Vue3 的createApp是独立的多个 app 实例互不干扰Vue2 的全局配置是共享的。// Vue2 Vue.use(ElementUI) new Vue({ render: h h(App) }).$mount(#app) // Vue3 const app createApp(App) app.use(ElementPlus) app.mount(#app)这个改动让微前端场景下的隔离性好了很多多个 Vue 应用可以共存。7. 生态工具链的全面升级7.1 构建工具从 webpack 到 ViteVue2 时代主流是 webpack配置复杂冷启动慢。Vue3 官方推荐 Vite基于 ESBuild 预构建开发服务器启动基本是秒级。npm create vitelatest my-app -- --template vue cd my-app npm install npm run dev我实测一个中等项目webpack 冷启动 30 秒Vite 只要 2 秒。热更新也是毫秒级体验差距巨大。7.2 状态管理Vuex 到 PiniaVuex 在 Vue3 里还能用但官方推荐 Pinia。Pinia 的 API 更简洁没有 mutation直接改 stateTypeScript 支持也好。// Pinia import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ name: }), actions: { setName(name) { this.name name } } })Vuex 的 mutation 那套在 Pinia 里被砍掉了少写一半代码。7.3 路由Vue Router 4Vue Router 4 配合 Vue3createRouter替代了new Routermode: history改成了history: createWebHistory()。import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [...] })7.4 UI 库的对应关系Vue2 生态Vue3 对应Element UIElement PlusVant 2Vant 4Ant Design Vue 1.xAnt Design Vue 3.xVuex 3PiniaVue Router 3Vue Router 4webpackVite注意Element Plus 的按需引入要用unplugin-vue-components和 Element UI 的babel-plugin-component完全不一样迁移时别照搬老配置。8. 性能表现的实测对比8.1 打包体积Vue3 的 runtime 比 Vue2 小了约 40%因为 Tree-shaking 支持更好。你没用到的 API 不会被打进包里。我那个后台项目迁移后vendor 包从 1.2MB 降到 780KB。8.2 渲染性能Vue3 引入了编译时优化比如静态提升、Patch Flag、缓存事件处理函数。静态节点只创建一次更新时跳过 diff。场景Vue2Vue3静态节点更新参与 diff跳过事件绑定每次重新绑定缓存大列表渲染较慢快约 1.3-2 倍内存占用较高较低8.3 响应式性能Proxy 的懒代理让深层对象的初始化快很多。我测过一个 5000 条数据的列表Vue2 初始化要 800msVue3 只要 300ms 左右。9. 迁移策略老项目怎么平滑升级9.1 先评估别硬上不是所有项目都值得迁移。如果项目稳定、不迭代、没性能问题别动它。迁移的成本主要在第三方库兼容和自定义指令重写上。9.2 用 vue/compat 做过渡Vue3 提供了兼容构建vue/compat能模拟 Vue2 的行为让你一步步改。// vite.config.js export default { resolve: { alias: { vue: vue/compat } } }它会针对每个不兼容的用法给出警告你按警告逐个改改完再切回正式版。9.3 迁移顺序建议先升级构建工具到 Vite跑通再说。升级 Vue Router 和状态管理。逐个组件改 API 风格优先改复杂组件。处理第三方库找 Vue3 版本或替代品。最后清理兼容层。9.4 常见坑点this在 setup 里不可用所有数据要通过返回值暴露。ref在模板里自动解包在 JS 里要.value这个最容易忘。reactive解构会丢失响应式要用toRefs。v-model的 prop 名变了自定义组件必须改。过滤器filter被移除用 computed 或方法替代。10. 常见问题与排查技巧实录10.1 响应式丢失怎么办最常见的是reactive解构。比如const state reactive({ count: 0 }) const { count } state // count 失去响应式解决用toRefs(state)或者直接用ref。10.2 ref 和 reactive 怎么选我的经验基本类型用 ref对象用 reactive但为了统一我基本全用 ref。ref 在模板里自动解包在 JS 里.value习惯就好。reactive 解构麻烦还容易丢响应式。10.3 组件不更新怎么排查先看数据是不是响应式的再看是不是在setup外修改的。Vue3 的onRenderTriggered能直接告诉你哪个依赖触发了渲染。10.4 第三方库不兼容比如vue-ueditor-wrap在 Vue3 里版本冲突得找 Vue3 版本或者换wangeditor/editor-for-vue。vue-konva在 Vue3 里要用vue-konva3。这类问题没有通用解只能一个个查。10.5 常见问题速查表问题原因解决数据改了页面不更新用了非响应式数据用 ref/reactive 包裹v-model 失效prop 名没改用 modelValue生命周期不执行钩子名没加 on改成 onMounted 等多根节点报错Vue2 写法Vue3 支持检查版本打包后白屏路由 mode 配置用 createWebHistoryTS 类型报错类型推导问题用 defineProps 泛型提示迁移时先把 Vue Devtools 升级到支持 Vue3 的版本不然调试会很痛苦。11. 我个人的迁移体会那次迁移做完最大的感受是Vue3 的 Composition API 一旦用顺了就回不去了。逻辑复用、类型推导、代码组织都比 Vue2 舒服太多。但迁移本身不是技术问题是工程问题——你得评估成本、排优先级、处理兼容。如果让我给建议新项目直接上 Vue3 Vite Pinia TypeScript别犹豫。老项目如果还在迭代用vue/compat渐进迁移别想着一次性重写。如果项目已经冻结那就别动稳定压倒一切。最后分享一个小技巧迁移时把setup里的逻辑按功能拆成自定义 hook比如useTable、useForm、useDialog这样不仅迁移顺利后续维护也轻松。我那个项目拆了十几个 hook代码量少了三成可读性反而更好。
返回列表