Vue2 和 Vue3 区别和学习先后顺序保姆级教程
如果你点进这篇文章,大概率是准备入门前端框架,或者在Vue2和Vue3之间纠结到底先学哪个。这事我太有发言权了,这些年带过不少新人,见过太多在Vue版本选择上反复横跳最后浪费大量时间的同学。坦白讲,Vue2和Vue3的区别不仅仅是语法层面的升级,它俩背后的设计思想、生态体系、甚至招聘市场的需求都完全不同。这篇文章我会把Vue2和Vue3的核心差异一次性讲透,再结合我带新人的实际经验,给出一个可以直接照抄的学习路径和先后顺序建议,然后把环境配置、路由、播放m3u8这些高频实操场景全部过一遍。文章会有点长,但保证全程干货,不走神。
先说一下我自己的背景,免得你怀疑我是不是纸上谈兵。我大概从Vue2.0发布那年开始写Vue,后来Vue3还在RFC阶段的时候就开始跟进Composition API的提案,上线过Vue2的老项目也有Vue3从零搭建的新项目,面试别人的时候也经常拿这两个版本的区别来考察候选人的技术深度。所以下面写的内容,都是我在实际开发和带人中验证过的东西,不是照着文档念一遍。
1. 先搞清楚方向:Vue3到底比Vue2强在哪里
要说学习顺序,首先得知道两者本质差异在哪。很多人对Vue3的印象停留在“性能更快、体积更小”,这个说法没错,但太笼统了。我们得先把Vue3的底层逻辑拆开看,你才知道为什么它值得学,也才知道Vue2的哪些知识是可以直接迁移过来的。
1.1 两个时代的核心分水岭:数据响应式原理
这是Vue2和Vue3最核心、也是最底层的一个区别。Vue2用的是 Object.defineProperty 去劫持对象属性的getter和setter,Vue3改成用ES6原生的 Proxy 来实现响应式。这两个东西虽然都在做“数据变了通知视图更新”这件事,但实现方式完全不同,导致的能力边界也完全不同。
Object.defineProperty 有几个天然短板。第一,它只能劫持对象上已经存在的属性,所以你给data里不存在的对象新增属性时,Vue2是侦测不到的,必须用 $set 这个方法手动触发响应式更新。第二,它无法直接监听数组索引变化和数组长度的变化,所以Vue2不得不去改写数组的 push、pop、splice 这些方法来变相实现数组响应式。第三,Vue2初始化的时候需要递归遍历整个data对象去给每个属性做劫持,对象层级越深,初始化性能越差。
Vue3用 Proxy 代理整个对象,而不是遍历每个属性,所以新增属性、删除属性、数组索引修改都能被原生侦测到。这意味着Vue2里的 $set、$delete 这种偏方在Vue3里彻底不需要了。同时 Proxy 是懒响应的,访问到某个属性时才对其做响应式处理,性能上比Vue2那种上来就递归遍历的方式好很多。
我举个真实例子。之前维护一个Vue2老项目,有一个弹窗组件,打开时要根据后端返回的字段动态往表单数据里加字段。后端偶尔少返回一个字段,前端 this.form.xxx = value 赋完值,页面死活不更新,最后只能老老实实 this.$set(this.form, 'xxx', value)。到了Vue3里,这种问题天然就不存在了,直接赋值就完事。
1.2 Composition API 和 Options API 的区别
Vue2写业务逻辑用的是Options API,也就是 data、computed、methods、watch、created 等选项放在一起,按类型去组织代码。代码少的时候还好,一旦组件大了,你会发现一个功能的代码被拆散到各个选项中,看的时候要在文件的各个区块之间跳来跳去,维护成本极高。
Vue3的Composition API用 setup 函数作为入口,鼓励你按照“逻辑关注点”而不是“选项类型”去组织代码。你可以把一个业务功能相关的响应式数据、计算属性、监听器、方法封装成一个独立的函数,多个组件之间还能复用这些函数。这个其实就是把React Hooks的思路借了过来。
这里我多说一句,很多教程把Options API和Composition API对立起来讲,其实不太准确,因为Vue3是兼容Options API的,你在Vue3里照样可以写 data、methods、computed。我个人的感受是:几十行的小组件,用Options API反而简洁直观;超过两百行甚至更多的复杂组件,Composition API的优势非常明显。这也是为什么后来有了 <script setup> 这个语法糖,它让你在单文件组件里写Composition API的时候能少写很多模板代码,现在已经是Vue3开发的主流写法了。
1.3 全局API和生态的变化
Vue2创建应用的方式是 new Vue({...}),Vue3改成了 createApp({...})。看起来只是一行代码的变化,背后影响却很大。Vue2的全局配置会污染所有实例,比如 Vue.use()、Vue.mixin() 注册的插件和混入会影响每一个新建的Vue实例。Vue3引入应用实例(app)的概念之后,你调用 app.use()、app.mixin() 只影响当前这个应用实例,多个应用实例之间互不干扰,这对于大型项目多实例共存、微前端拆分的场景是实打实的优化。
另外一个容易被忽略的点是生态迁移。Vue2时代的 vue-router@3、vuex@3、Element UI 到了Vue3都要换成对应新版本,比如 vue-router@4、pinia、Element Plus。这些库在API上也有不少调整,最典型的就是vue-router从 new Router() 改成 createRouter(),vuex在Vue3里用 createStore(),而且官方主推的Pinia直接内置了对Composition API的支持。所以如果你只会Vue2而不了解这些生态的新写法,简历上写熟练Vue,实际上手Vue3项目会是完全陌生的感觉。
2. 逐点拆解:Vue2与Vue3的10个关键差异
前面讲了大方向的几个架构级差异,这一节我把日常开发中能碰到的具体差异点列出来。每一个都是面试官爱问的,也是你从Vue2往Vue3迁移时一定会踩到的。
2.1 v-model用法差异:不止是语法糖那么简单
Vue2的 v-model 本质是 :value 加 @input 的语法糖,用在自定义组件上时,默认情况下子组件接收的prop叫 value,抛出的事件名是 input。你可以在组件里通过 model: { prop: 'checked', event: 'change' } 来修改默认的prop名和事件名。
Vue3里,v-model 的prop名变成了 modelValue,事件名变成了 update:modelValue。而且Vue3支持一个组件上写多个 v-model,比如 v-model:name="name" 和 v-model:age="age",分别对应 name 和 age 两个prop的绑定。这在Vue2里理论上可以用 sync 修饰符实现,但写法上麻烦不少。
如果你是从Vue2迁移到Vue3,自定义组件上的 v-model 是一个非常大的坑。我见过不少同事把老项目搬过来后,子组件里的 value prop收不到值,排查半天发现是Vue3已经改了默认prop名。记住Vue3的规则就行:默认用 modelValue 作为prop名,用 update:modelValue 作为事件名。
2.2 生命周期钩子的调整:新增、改名、对应关系有变化
Vue3的生命周期变化是高频考点,具体差异如下表:
| Vue2钩子 | Vue3钩子 | 说明 |
|---|---|---|
| beforeCreate | 使用 setup() | setup替代了beforeCreate和created |
| created | 使用 setup() | 同上 |
| beforeMount | onBeforeMount | 挂载前 |
| mounted | onMounted | 挂载完成 |
| beforeUpdate | onBeforeUpdate | 数据更新前 |
| updated | onUpdated | 数据更新完成 |
| beforeDestroy | onBeforeUnmount | 组件卸载前,注意改名 |
| destroyed | onUnmounted | 组件卸载后,注意改名 |
| errorCaptured | onErrorCaptured | 捕获子组件错误 |
| activated | onActivated | keep-alive 缓存激活 |
| deactivated | onDeactivated | keep-alive 缓存失活 |
注意 beforeDestroy 和 destroyed 在Vue3里改名成了 onBeforeUnmount 和 onUnmounted,寓意是组件实例不再用销毁这个概念,而是用卸载,语义上更准确。beforeCreate 和 created 在Vue3的Composition API写法中已经不存在了,因为 setup 就是组件创建阶段的入口,你在 setup 里可以拿到所有需要的东西,包括props、context,响应式数据初始化也在这里完成。
有个容易忽略的点:Vue3的 setup 是在 beforeCreate 之前执行的,所以在 setup 里拿不到 this。很多刚转Vue3的同学在 setup 里写 this.xxx 会报错,不用慌,这是设计如此,后面所有东西都从参数 props、context 里取。
2.3 异步组件与Suspense:组件加载体验的升级
Vue2里定义一个异步组件,常规做法是 components: { Demo: () => import('./Demo.vue') },配合 loading 和 error 还能做简单的加载状态。Vue3保留了这个写法,但推荐用 defineAsyncComponent 去定义,并且配合内置组件 Suspense 来做更优雅的异步处理。
Suspense 可以理解为一个“等待区”,它可以包裹多个异步组件,在它们全部加载完成之前展示 fallback 中的内容,加载完之后展示真实内容。这就让“页面级别的加载过渡”变得非常简单,不用再为每个异步组件单独写状态。实际项目中,我用 Suspense 做过嵌套路由的页面级loading,效果很丝滑,代码量比Vue2时代少了很多。
2.4 另外几个容易被忽略的零散差异
$children被移除了,现在访问子组件实例需要借助ref和defineExpose。Vue2里用this.$children拿子组件数组的做法,在Vue3里没有了,因为这种依赖组件树结构的方式在组合式API里是反模式。- Fragment支持。Vue2的组件模板必须有根节点,不然会报错。Vue3允许组件模板有多个根节点,这个特性叫Fragment。做布局组件的时候非常爽,再也不用为了凑一个根节点去包一层无意义的
div。 - Teleport。这个组件可以把内容传送到指定的DOM节点下,经常用来做弹窗、下拉框、loading遮罩。Vue2时代处理弹窗挂载位置是个麻烦事,常常要手动操作DOM,Vue3内置
Teleport直接解决了。 - 过滤器
filter被移除了。Vue2里用filters: { formatTime }然后在模板里{{ time | formatTime }}的写法,Vue3不再支持,官方建议用计算属性或方法替代。 - 事件API调整。Vue2的
$on、$off、$once在Vue3实例上被移除了,现在推荐用外部的事件总线库或者直接用组合式API的状态管理来解决跨组件通信。
上面这些差异,不需要一次性全部记牢,建议你写代码时遇到哪个就去查哪个,多踩几次坑自然就记住了。下面我用一个表格把所有差异汇总一下,方便你收藏后当速查表用。
| 对比项 | Vue2 | Vue3 |
|---|---|---|
| 响应式方案 | Object.defineProperty | Proxy,支持新增/删除属性 |
| API风格 | Options API | Options API + Composition API |
| 创建应用 | new Vue() | createApp() |
| 全局API | Vue.use / Vue.mixin | app.use / app.mixin,多实例隔离 |
| v-model默认值 | value + input | modelValue + update:modelValue |
| 生命周期 | beforeDestroy / destroyed | onBeforeUnmount / onUnmounted |
| 多根节点 | 不支持 | 支持Fragment |
| 过滤器 | 支持filter | 移除 |
| $children | 支持 | 移除 |
| 异步组件 | 简单import | defineAsyncComponent + Suspense |
| 表格为什么这么列 | 方便对照 | 方便对照 |
3. 学习先后顺序:我的建议是先学Vue3,但别跳过Vue2
这个问题每年都有很多人问,也是我写这篇文章最想重点讲的部分。先说结论:如果你是完全零基础,直接学Vue3,但学的时候要有意识地了解Vue2的核心概念;如果你已经在维护Vue2项目,那么优先把Vue3的新特性过一遍,同时培养新旧代码的“翻译”能力。这个建议不是什么行业共识,而是我结合招聘市场、项目现状、学习曲线三个维度思考后得出的答案。
3.1 为什么我建议零基础直接学Vue3
第一个原因是时间成本。Vue3已经发布好几年了,生态早就成熟稳定,Element Plus、Vant等主流组件库、UI库全面跟进,vue-router@4、Pinia也都很成熟。你现在学Vue2,虽然能在老项目上立即用上,但等你学完再接触新项目,还是要花时间学Vue3,相当于学了两遍框架的核心思路。而直接学Vue3,你学的就是当下和未来主流的东西,不用走回头路。
第二个原因是思维方式的正确性。Vue3的Composition API强调逻辑复用和关注点分离,这个思维在React、Vue、甚至小程序开发中都是通用的。你如果一开始就用Composition API写代码,会慢慢养成按业务功能组织代码的习惯,这个习惯比框架本身值钱得多。反观Vue2的Options API,它的写法很依赖this,对初学者不太友好,容易绕晕。
第三个原因是面试和求职。现在招聘基本都要求Vue3,虽然很多公司还有维护Vue2老项目的需求,但面试时聊Vue3的新特性、响应式原理、Composition API、Pinia几乎是必考。你简历上写“熟练使用Vue3”显然比“会Vue2”更有竞争力。
3.2 那了解Vue2还有什么意义
如果你完全跳过Vue2,会有一个很现实的痛点:老项目太多了。国内现在有大量生产项目还是Vue2写的,很多公司短期没有重构计划。你入职后如果碰到这些项目,连 this.$set 为什么这么写、Vue.use 和 app.use 有什么区别都看不明白,会很被动。
所以我对“零基础先学Vue3”的补充是:学Vue3的时候,要同步理解Vue2是如何一步步演进到Vue3的,也就是“为什么Vue3要这么改”。比如Vue3为什么用Proxy而不用defineProperty,为什么新增了Composition API,为什么移除了过滤器。这样你既能写好Vue3,也具备了回头读Vue2老代码的能力。
具体来说,你的学习可以这样安排:
- 第一周:搭建一个Vite + Vue3项目,熟悉项目结构,用Composition API写一个简单的TodoList,注意体会
setup、ref、reactive、computed之间的配合。 - 第二周:学Vue Router4和Pinia,做一个多页面完整项目,比如商城后台,涉及列表、详情、登录态管理。
- 第三周到第四周:做项目扩展,引入Element Plus,做接口封装、权限控制、路由守卫,同时深入了解响应式原理和组件通信。
- 碎片时间:回头了解Vue2的核心知识点,尤其是生命周期、指令、Mixin,目的是能看懂老代码和面试题。
3.3 如果你已经在维护Vue2项目怎么办
这类同学我见得太多了,项目暂时不重构,但外面机会要求Vue3,两边都要兼顾。我的建议是以Vue3为主学习,Vue2为辅巩固。你Vue2已经会用,底子应该在,Vue3对于你最大的学习难度是Composition API和响应式API的使用习惯。你可以先把一个Vue2项目里的某个组件用Vue3的Composition API重写一遍,不去管组件库和路由的差异,只关注逻辑部分。这样你既能感受到Vue3代码的组织方式,也顺手把逻辑迁移的能力练了。
要注意,Vue2和Vue3虽然同叫Vue,但它们在组件通信、路由、状态管理这些层面的配合方式有差异。你在Vue2里已经养成的某些习惯,比如用 this.$router.push、this.$store.dispatch,到了Vue3里依然能用,但更推荐用组合式API里提供的 useRouter、useStore。这种“翻译”能力是你在新旧项目之间切换的关键。
4. 环境配置实操:用Vite从零搭建Vue3项目
学习顺序说完了,该动手了。不管你学Vue2还是Vue3,第一步都是安装环境。新人最容易在这一步栽跟头,因为网上很多教程还在用webpack那套,现在Vite已经是主流。我下面完整走一遍环境配置流程,包含Node环境、创建项目、配置路由、集成pinia,最后加一个播放m3u8的实战案例,这正好对应最近热搜词里那几个高频需求。
4.1 安装Node.js环境:先检查再做决定
Vue3项目要求Node版本至少是16以上,推荐直接用18 LTS或20 LTS版本。注意“LTS”是长期维护版本,稳定性最好,千万别为了追新去下最新版,有些依赖可能还没适配。
在命令行执行 node -v 和 npm -v,确认自己有没有安装Node以及版本。没有的话,去Node官网下对应操作系统的安装包,一直点下一步即可。装完之后建议顺手把npm源切换到国内镜像,这一步能避免后面下载依赖时慢到怀疑人生:
bash复制npm config set registry https://registry.npmmirror.com
切换之后你可以用 npm config get registry 验证一下,如果输出上面那个地址就说明成功了。
4.2 用Vite创建Vue3项目:五秒起一个项目
Node装好之后,创建Vue3项目最简单的命令如下:
bash复制npm create vue@latest
注意这个命令创建的是基于Vite的Vue3项目,而且是通过官方脚手架 create-vue 生成的。执行之后它会问你项目名字、是否集成TypeScript、是否配置Vue Router、Pinia、ESLint等,按需选择即可。如果你想开箱即用带路由和状态管理的结构,可以这样选:
bash复制# 项目名称输入 demo
# ✔ 是否使用TypeScript? No
# ✔ 是否启用Vue Router? Yes
# ✔ 是否启用Pinia? Yes
# ✔ 是否启用ESLint? 看你习惯,建议Yes
生成完成后执行:
bash复制cd demo
npm install
npm run dev
浏览器访问终端输出的地址,就能看到一个Vue3的初始页面。Vite的开发服务器响应速度非常快,比webpack时代的 npm run serve 体验好太多。原因就是Vite基于原生ESM,按需编译,项目越大优势越明显。
4.3 配置Vue Router4:路由写法变化一次讲清
Vue3路由已经从 vue-router@3 升级到了 vue-router@4,API有不少变化。如果你用 npm create vue@latest 创建项目时选择了Vue Router,脚手架已经帮你配好了,但你应该知道它背后的写法,否则后面配置动态路由、路由守卫会非常吃力。
最简单的路由配置长这样,在一个单独的 router/index.js 里:
javascript复制import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue')
},
{
path: '/about',
name: 'about',
component: () => import('@/views/AboutView.vue')
}
]
})
export default router
和Vue2对比,核心变化是:new Router() 换成了 createRouter(),mode参数换成了history,hash 模式对应的是 createWebHashHistory()。懒加载组件写法从 import('@/views/HomeView.vue') 变成了箭头函数 component: () => import(...),这个写法和Vue2的懒加载其实差不多,只是Vue3的组件定义方式更统一。
路由守卫的写法也有变化。Vue2里是 router.beforeEach((to, from, next) => {...}),Vue3依然支持这种写法,同时新增了可选返回值的风格:
javascript复制router.beforeEach((to, from) => {
// 如果返回 false 则取消导航
return true
})
next 参数在Vue3中不再是必须的,能不用就不用,简化了很多回调逻辑。
4.4 在Vue3项目中播放m3u8视频流:hls.js接入流程
很多视频类项目会碰到播放m3u8格式的需求,尤其是直播回放和视频点播场景。m3u8是HLS协议的一种索引文件格式,浏览器原生不支持直接播放,需要借助hls.js这个库来做转封装。Vue3里接入hls.js的流程很简单,我直接给你一个完整组件示例。
先安装依赖:
bash复制npm install hls.js
然后创建一个播放器组件,比如 HlsPlayer.vue。
vue复制<template>
<div class="player">
<video ref="videoRef" controls class="video"></video>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import Hls from 'hls.js'
const props = defineProps({
src: {
type: String,
required: true
}
})
const videoRef = ref(null)
let hls = null
onMounted(() => {
if (!videoRef.value) return
if (Hls.isSupported()) {
hls = new Hls()
hls.loadSource(props.src)
hls.attachMedia(videoRef.value)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoRef.value.play().catch(() => {})
})
} else if (videoRef.value.canPlayType('application/vnd.apple.mpegurl')) {
// Safari 原生支持 HLS
videoRef.value.src = props.src
videoRef.value.addEventListener('loadedmetadata', () => {
videoRef.value.play().catch(() => {})
})
}
})
onBeforeUnmount(() => {
if (hls) hls.destroy()
})
</script>
<style scoped>
.video {
width: 100%;
height: auto;
background: #000;
}
</style>
使用的时候:
vue复制<template>
<HlsPlayer src="https://example.com/live/stream.m3u8" />
</template>
这里有几个坑要提醒你。第一,hls.destroy() 一定要在组件卸载时调用,否则视频流会一直占用网络资源,刷新页面后可能看到连接数量超限的错误。第二,如果视频地址跨域,需要服务端在响应头里加上CORS相关的字段,不然hls.js在解析m3u8时直接就报错了。第三,Safari原生的HLS播放能力很强,但其他浏览器还是建议走hls.js,做兼容判断的模板代码建议直接保留,省得后续多端联调出问题。
5. 面试题速查:Vue2/Vue3区别高频考点整理
标题里提到了Vue面试题,那我顺手把Vue2和Vue3区别这个方向的常见面试题整理成一份速查表,你可以面试前过一遍。这些问题我都实际在面试中问过,也被人问过,覆盖面比较广。
| 面试题 | 高分回答要点 | 常见错误 |
|---|---|---|
| Vue2和Vue3的响应式原理有什么区别 | Vue2用Object.defineProperty逐属性劫持,改写了数组方法;Vue3用Proxy代理整个对象,支持新增删除属性和数组索引监听 | 只答“Proxy性能更好”,没解释为什么 |
| 什么是Composition API,它解决了什么问题 | Options API按类型组织代码,组件复杂后逻辑分散;Composition API按功能组织逻辑,方便复用和抽取 | 回答太抽象,没举例具体场景 |
| setup函数里为什么不能用this | setup执行时组件实例还没初始化完成,它发生在beforeCreate之前 | 答“Vue3故意取消了this”,不够准确 |
| ref和reactive怎么选择 | ref可以处理基本类型和引用类型,取值用.value;reactive只能处理对象类型,支持深层响应式 | 忘记说ref的.value,以及reactive的深层响应式限制 |
| Vue3移除了哪些Vue2的API | $children、filter、$on/$off/$once、事件总线写法、Vue.use全局注册 | 只回答过滤器,忽略其他 |
| v-model在Vue3中有什么变化 | prop名从value变成modelValue,事件从input变成update:modelValue,支持多个v-model | 回答“用法不变”直接就错了 |
| 生命周期在Vue3里改了哪些名字 | beforeDestroy和destroyed改名成onBeforeUnmount和onUnmounted | 记混beforeMount和beforeDestroy的位置 |
| 为什么Vue3引入Suspense | 解决异步组件加载的等待状态管理,配合defineAsyncComponent使用 | 不知道Suspense是什么,直接卡住 |
面试题不在多,关键是你确实能讲清楚“为什么”。我经常遇到候选人背得出答案,但一问到底层原理就答非所问。建议你复习的时候,多问自己一句“Vue3为什么要这样设计”,想通了这个问题,很多题目可以举一反三。
6. 常见问题与排查技巧实录
最后这部分是纯经验输出。我在带人、写代码、维护项目过程中,见过太多Vue2和Vue3混用导致的奇怪问题,下面挑几个最典型的分享出来,顺便给排查思路。你以后要是遇到类似的,可以直接按这个思路去查。
6.1 setup里监听不到数据变化,问题出在哪
有同事在 setup 里这么写:
javascript复制setup(props) {
watch(() => props.count, (newVal) => {
console.log(newVal)
})
}
结果发现 props.count 变化时根本没有触发回调。原因很可能是他传的props是从store中获取的,但父组件没有正确响应数据更新。排查思路:第一步,确认父组件传给子组件的prop本身是否在变化,可以在父组件模板里先渲染一下看看;第二步,确认watch的getter函数是不是写成了 props.count 而不是 () => props.count;第三步,如果props是从store中map出来的,确认用的是Pinia的setup写法而不是Vue2风格的mapState。
6.2 响应式丢失:解构赋值是重灾区
Vue3里用 reactive 定义对象,如果你直接解构对象,解构出来的变量就不是响应式的了:
javascript复制const state = reactive({ count: 0 })
const { count } = state // count 不是响应式的!
count++ // 页面不会更新
很多人刚开始会踩这个坑。解决方式有三种:一是用 toRefs 转换后再解构;二是直接用 state.count 操作;三是干脆用 ref 定义基础数据类型。我建议初学者多用 ref,因为它的响应式不会因为解构而丢失,且心智负担更小。等你对Vue3的响应式API熟悉了,再在合适的场景用 reactive 配合 toRefs。
6.3 v-model在自定义组件上的绑定不生效
Vue2迁移Vue3时最常见的bug,前面提过:Vue3里自定义组件默认的v-model绑定的prop是 modelValue,而不是Vue2时代的 value。如果你的子组件还在用 props: ['value'] 接收,那 v-model 收不到任何数据。
排查办法很直观,打开Vue Devtools,看一下组件实例上的props列表里有没有 modelValue,没有就说明你说法不对。如果子组件是从老项目复制过来的,建议直接把prop名改成 modelValue,事件名改成 update:modelValue,不要用 model 选项去硬凑,保持规范才能减少后续协作的沟通成本。
6.4 用Vue2的写法写Vue3,居然也能跑但结果不对
还有一类问题最难排查:代码能跑,就是结果不符合预期。比如你在Vue3项目里用 Vue 全局变量去注册插件,代码没报错,但插件没有真正生效。原因是Vue3里 Vue 这个全局对象只是一个API的集合,创建应用实例必须通过 createApp,很多全局API都不能再通过 Vue.xxx 调用了。
另一个典型的例子是 this.$set。Vue3里 this 上根本没有 $set,如果你在Options API的 methods 里写了 this.$set,它可能不报错,因为Vue3可能保留了原来的一些选项兼容,但永远不会按Vue2的方式去触发响应式更新。我遇到过一个同事,把Vue2的动态加字段逻辑直接搬到Vue3,页面一直不更新,后来把 $set 去掉,直接赋值,问题迎刃而解。所以,迁移到Vue3之后,第一件事就是忘掉Vue2的那些特殊API,用原生赋值,用响应式API自带的工具函数,这才符合Vue3的设计。
最后说点实在的
写了这么多,核心还是那句话:Vue3已经是大势所趋,但Vue2的老项目你躲不开。最好的策略是两条腿走路,主学Vue3,用一套“新旧对比”的思维去理解Vue2。你学到的响应式原理、组件通信、路由配置这些底层知识,在两个版本里是通用的,只是API的外壳变了。每次遇到差异点,别急着背,先想想Vue3为什么这么改,想通了之后你再看Vue2代码,反而会豁然开朗。现在我带的几个新人,基本都是这个节奏,三到四周就能上手实际业务开发。希望这篇保姆级教程能帮你少走那几趟弯路。
