Vue2 转 Vue3 最容易踩的 10 个坑(高频报错+解决办法)
我直接给你最实用、最常遇到、一踩就报错的坑,每个都告诉你现象 + 为什么错 + 怎么改,看完直接避开 90% 的问题。
1. 响应式相关:直接赋值丢失响应式(最常见!)
<script setup>
import { reactive } from 'vue'
let obj = reactive({ name: '张三' })
// 直接赋值 = 响应式没了!
obj = { name: '李四' }
</script>
现象:页面不更新,数据变了但视图不变。
原因:reactive 创建的对象被整个替换,代理失效。
解决:
- 用
ref定义 - 或者修改内部属性,不替换整个对象
// 正确写法1
let obj = ref({ name: '张三' })
obj.value = { name: '李四' }
// 正确写法2
obj.name = '李四'
2. 模板里不用 .value,脚本里必须写 .value
const count = ref(0)
console.log(count) // 打印出来是个对象,不是 0
现象:拿到的值是 RefImpl,不是数字/字符串。
解决:
<template>不用写 .value<script setup>必须写 .value
3. 生命周期函数改名 + 只能在 setup 里用
export default {
mounted() { ... } // 可以用
}
// 但在 script setup 里不能这么写!
解决:
import { onMounted } from 'vue'
setup() {
onMounted(() => {
// 只能写在这里
})
}
改名对照表:
- beforeDestroy → onBeforeUnmount
- destroyed → onUnmounted
4. 事件总线 off / $once 被移除了
// Vue2
this.$on('event', () => {})
// Vue3 直接报错:undefined
解决:
- 用第三方库 mitt
- 或者用 Pinia/Vuex
- 或者父传子 + 子传父
5. 过滤器 filters 被移除了
<!-- Vue2 -->
<div>{{ msg | filter }}</div>
Vue3 不支持
解决:
- 用 computed 替代
- 用 方法调用 替代
const formatMsg = computed(() => msg.value.toUpperCase())
6. this 变了!setup 里没有 this
setup() {
this.$router // 报错 undefined
}
原因:setup 执行时组件还没创建,没有 this。
解决:
import { useRouter } from 'vue-router'
const router = useRouter()
router.push('/')
7. delete 不需要了,但也用不了了
// Vue2 必须写
this.$set(obj, 'age', 18)
// Vue3 直接写就行
obj.age = 18
原因:Vue3 用 Proxy,天然支持新增/删除属性监听。
8. 根组件创建方式变了
// Vue2
new Vue({ el: '#app' })
// Vue3 报错
解决:
import { createApp } from 'vue'
createApp(App).mount('#app')
9. 多根节点(片段)支持,但要注意
Vue2 模板必须只有一个根节点
Vue3 可以多个根节点
<template>
<div></div>
<div></div>
</template>
注意:如果组件被父组件用 v-for 绑 key,会有警告。
10. 自定义指令生命周期变了
// Vue2
bind, inserted, update...
// Vue3 全变了
created, beforeMount, mounted, beforeUpdate, updated...
这个坑一般写自定义指令才会遇到。
最最最精简的 5 条保命口诀(背下来)
- reactive 不能直接赋值,ref 脚本要写 .value
- setup 没有 this,获取 router/store 用 hook
- set 全删了
- 生命周期变成 onXXX,且只能在 setup 调用
- 多根节点允许,但小心样式和 key