从Vue2一路走来,Vue3到底变了什么?值不值得升级?一个老Vue开发者的真实心路历程。
说实话,2020年Vue3刚发布的时候,我是有点抵触的。
用了五年Vue2,项目跑得好好的,组件库生态也成熟,干嘛要折腾?组合式API那一堆ref、reactive看着就头大,感觉是在写React。
但后来...真香。
这篇文章就是写给和当年的我一样的Vue2老用户的,聊聊Vue3到底改了什么,以及我为什么觉得值得升级。

Vue3官网:The Progressive JavaScript Framework
不后悔,甚至有点相见恨晚。
但这个结论需要一些前提:
下面展开说说为什么。
用了这么久Vue2,其实也习惯了它的一些"小毛病"。但换到Vue3之后回头看,才发现这些问题确实挺膈应人的。
Vue2用的是Object.defineProperty来实现响应式,这玩意儿有个先天缺陷——没法监听新增属性和数组索引变化。
// Vue2的日常
export default {
data() {
return {
user: { name: '张三' }
}
},
methods: {
addAge() {
// ❌ 这样写不会触发更新
this.user.age = 25
// ✅ 必须用$set
this.$set(this.user, 'age', 25)
},
updateList() {
// ❌ 这样改数组也不会更新
this.list[0] = 'new value'
// ✅ 还是得用$set
this.$set(this.list, 0, 'new value')
}
}
}每次写到这里我都想吐槽:凭什么给对象加个属性还要调方法?
Vue2的选项式API把同一个功能的代码拆得七零八落。
比如我要实现一个"搜索功能",代码分布是这样的:
export default {
data() {
return {
keyword: '', // 搜索关键词在这
results: [], // 搜索结果在这
loading: false // 加载状态在这
}
},
computed: {
hasResults() { // 计算属性在这
return this.results.length > 0
}
},
methods: {
async search() { // 搜索方法在这
this.loading = true
this.results = await api.search(this.keyword)
this.loading = false
}
},
watch: {
keyword(newVal) { // 监听器在这
this.debouncedSearch()
}
},
created() { // 生命周期在这
this.debouncedSearch = debounce(this.search, 300)
}
}一个搜索功能,代码分散在5个地方。组件简单还好,一旦复杂起来,你得上下来回跳着看代码,看得人眼花。
Vue2复用逻辑主要靠mixins,但mixins有几个硬伤:
// userMixin.js
export default {
data() {
return {
user: null,
loading: false
}
},
methods: {
fetchUser() { /* ... */ }
}
}
// 组件中使用
export default {
mixins: [userMixin, permissionMixin, logMixin],
data() {
return {
loading: true // 这个loading和mixin里的冲突了,谁赢?
}
}
}问题来了:
this.user到底是哪个mixin的?看不出来我见过一个项目,一个组件引了8个mixin,出了bug根本不知道从哪找起。
Vue3换成了Proxy来实现响应式。Proxy可以拦截对象的所有操作,不管是读取、修改、新增还是删除。
// Vue3,直接写就完事了
const user = reactive({ name: '张三' })
// ✅ 新增属性,能响应
user.age = 25
// ✅ 删除属性,也能响应
delete user.name
// ✅ 数组索引,照样响应
const list = reactive(['a', 'b', 'c'])
list[0] = 'new value'再也不用记什么时候该用$set了。这一点我觉得就值回票价。

这是Vue3最大的变化。同样是搜索功能,用组合式API写是这样的:
<script setup>
import { ref, computed, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
// 搜索相关的代码全在一起
const keyword = ref('')
const results = ref([])
const loading = ref(false)
const hasResults = computed(() => results.value.length > 0)
async function search() {
loading.value = true
results.value = await api.search(keyword.value)
loading.value = false
}
const debouncedSearch = useDebounceFn(search, 300)
watch(keyword, debouncedSearch)
</script>一个功能的代码放在一起,不用在各个选项之间跳来跳去。
更重要的是,这些逻辑可以轻松抽取成可复用的函数:
// useSearch.js
export function useSearch(apiFn) {
const keyword = ref('')
const results = ref([])
const loading = ref(false)
async function search() {
loading.value = true
results.value = await apiFn(keyword.value)
loading.value = false
}
const debouncedSearch = useDebounceFn(search, 300)
watch(keyword, debouncedSearch)
return { keyword, results, loading, search }
}<script setup>
import { useSearch } from './useSearch'
// 一行搞定,来源清晰
const { keyword, results, loading } = useSearch(api.searchUsers)
</script>这比mixins清爽太多:

Vue3从底层就用TypeScript重写了,类型推导准确多了:
<script setup lang="ts">
interface User {
id: number
name: string
age?: number
}
// props有完整类型提示
const props = defineProps<{
user: User
readonly?: boolean
}>()
// emit也有类型约束
const emit = defineEmits<{
(e: 'update', user: User): void
(e: 'delete', id: number): void
}>()
// ref自动推导类型
const count = ref(0) // Ref<number>
const user = ref<User | null>(null) // Ref<User | null>
</script>在VS Code里写Vue3 + TS,自动补全和错误提示都很准,体验好了一个档次。
以前写Modal,要么把组件放到body下,要么和z-index、overflow斗智斗勇。现在有Teleport:
<template>
<button @click="showModal = true">打开弹窗</button>
<!-- 内容传送到body下,但逻辑还在这个组件里 -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<h2>我是弹窗</h2>
<button @click="showModal = false">关闭</button>
</div>
</Teleport>
</template><!-- Vue2:必须有根节点 -->
<template>
<div>
<header>头部</header>
<main>内容</main>
<footer>底部</footer>
</div>
</template>
<!-- Vue3:多个根节点OK -->
<template>
<header>头部</header>
<main>内容</main>
<footer>底部</footer>
</template>Vue3在性能上做了很多优化,官方数据:

Vue的npm下载趋势持续增长
能用,完全能用。
Vue3是向后兼容的,选项式API在Vue3里一样跑得好好的:
<script>
// 这种写法在Vue3里完全没问题
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
</script>你不需要一次性把所有代码都改成组合式API,可以慢慢来。新功能用组合式,老代码不动它,完全可以。
变更项 | Vue2 | Vue3 |
|---|---|---|
创建应用 |
|
|
v-model默认prop |
|
|
v-model默认事件 |
|
|
事件总线 |
| 移除,用mitt等 |
过滤器 |
| 移除,用computed |
过渡class |
|
|
完整迁移指南:Vue3迁移文档
新项目:毫无疑问选Vue3。Vue2已经在2023年底停止维护了。
老项目:
个人技能:不管项目升不升,Vue3你都该学。这是Vue的未来方向,面试也会问。
作为Vue2老用户,建议按这个顺序学:
回到开头的问题:用了五年Vue2,转Vue3后悔了吗?
不后悔。
Vue3解决了Vue2的几个核心痛点:
$set当然,学习曲线是有的。但跨过去之后,你会发现真的香。
下一篇预告:Vue3生态全景图:除了Vue,你还需要认识这些朋友
Vite、Pinia、VueUse、Element Plus... Vue3全家桶都有谁?下一篇带你认识。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。