超详细完整版:React18 Hooks → Vue3 组合式API
每一个坑:现象 + 底层原因 + 为什么React不会这样 + 错误代码 + 正确代码
全部是你从React过来100%会踩、思维完全冲突、越写越崩溃的坑,不讲空话,全部讲透原理。
先记住最核心底层区别(所有坑的根源):
React:不可变数据 Immutable + 快照闭包 + 手动set更新
Vue3:Proxy响应式可变 + 直接修改值 + 代理自动更新
两套设计完全相反,只是语法长得像Hooks而已,你用React思维写Vue3,必崩。
坑1:reactive 解构对象 → 响应式永久消失(Vue3第一地狱坑)
现象
你习惯React解构,写Vue3:
js
import { reactive } from 'vue'
const user = reactive({
name: '张三',
age: 18
})
// React写法:下意识解构
const { age } = user
页面渲染 {``{ age }}
你修改 user.age = 20
数据变了,页面完全不更新!!!
原因(底层原理)
Vue3 reactive 是Proxy代理对象 ,响应式是绑在整个对象 上的。
解构 const { age } 等价于:
js
const age = user.age
你把代理对象里的原始基本值取出来 ,赋值给一个普通变量。
普通变量和Proxy代理彻底断开联系,不再是响应式。
React为什么不会有这个坑?
React useState 解构出来本身就是固定引用+快照 ,setState重新渲染重新解构,天然不会断响应式。
React开发者毫无防备,过来必踩。
错误写法(90%新人)
js
const { age } = reactive({ age:18 })
正确写法(toRefs)
js
import { reactive, toRefs } from 'vue'
const user = reactive({ age:18 })
const { age } = toRefs(user) // 把普通值转成ref响应式
坑2:reactive 不能直接整体替换赋值(和React完全相反)
现象
React你天天这么写:
js
const [user, setUser] = useState({ name:'a' })
setUser({ name:'b', age:20 }) // 整体替换,完全正常
到Vue3你照搬:
js
const user = reactive({ name:'a' })
user = { name:'b', age:20 } // ❌ 页面不更新!彻底失效!
原因
user只是一个变量名,指向Proxy代理地址- 你重新赋值
user = 新对象
→ 变量指向了普通新对象
→ 不再指向原来的Proxy代理 - Vue失去代理,响应式直接报废
Vue3 reactive只能改内部属性,不能替换整个代理。
正确写法
js
user.name = 'b'
user.age = 20
坑3:ref 忘记写 .value,天天页面不更新
现象
React:
js
const [count, setCount] = useState(0)
count = 100 // 语法错,但思路是直接用
Vue3:
js
const count = ref(0)
count = 100 // ❌ 无效!页面不动!
count.value = 100 // ✅ 才更新
模板里不用.value,JS逻辑必须.value,双重规则。
原因
ref 内部包装成了一个响应式对象 ,真实值存在 .value 属性里。
ref = 外层包装对象 + 内部真实值。
你直接改变量,改的是引用地址,不是内部值。
React没有包装对象,直接用值,所以你习惯性忘记.value。
坑4:ref 自动解包规则混乱,嵌套/数组里又不能直接.value
更坑的是:
- reactive内部的ref会自动解包,不用.value
- 数组里的ref不会自动解包,必须.value
- computed里自动解包
- 函数返回里不解包
你从React过来,完全没有这套规则,写一段错一段。
坑5:watch 监听逻辑 ≠ useEffect,完全反向(最容易逻辑写崩)
现象
React useEffect:
js
useEffect(()=>{
// 依赖变化执行
}, [age])
清晰、简单、浅层监听。
Vue3你照搬写:
js
const user = reactive({ age:18 })
watch(user.age, ()=>{ console.log('变化') }) // ❌ 完全不监听!
原因底层
Vue3 watch 分两种:
- 监听ref基本值:直接监听
- 监听reactive对象属性 :必须传getter函数,不能直接取值
因为 reactive 是Proxy,直接拿 user.age 拿到的是原始值,不是响应式引用。
正确写法
js
watch(() => user.age, ()=>{})
附加巨坑:reactive默认强制深度监听
React useEffect只监听依赖浅层变化。
Vue3 watch(reactive对象) 强制深度递归监听所有子属性 ,关都关不掉。
对象里随便改一个字段都会触发,性能爆炸、逻辑混乱。
坑6:setup执行时机极早,拿不到DOM(和React完全相反)
现象
React函数组件:函数执行 → 渲染DOM → useEffect执行
你在函数体里虽然拿不到DOM,但逻辑很顺。
Vue3 setup:
js
export default {
setup() {
// 执行时机:挂载DOM之前!!!
console.log(document.getElementById('box')) // null
}
}
setup 在所有生命周期最前面,DOM还没创建。
坑点
你习惯React在顶层写逻辑,过来直接在setup拿DOM、拿组件实例,永远null。
必须放到 onMounted 里。
执行顺序对比
React:渲染 → useEffect
Vue3:setup → beforeMount → mounted → DOM生成
完全反向。
坑7:闭包取值逻辑 完全相反(最反人类)
React闭包(经典)
React渲染一次生成一次快照,定时器永远捕获当时旧值
js
const [count, setCount] = useState(0)
setTimeout(()=>console.log(count), 1000) // 永远是当时旧值
Vue3闭包
Vue3是Proxy代理,定时器捕获的是代理引用 ,永远拿到最新值
js
const count = ref(0)
setTimeout(()=>console.log(count.value),1000) // 最新值
灾难后果
你用React闭包经验写Vue3:
- 想拿旧值 → 拿到新值
- 想拿新值 → 拿到旧值
逻辑完全颠倒,bug根本找不到原因。
坑8:computed 是只读的!和useMemo完全不一样
现象
React useMemo:
js
const double = useMemo(()=>count*2, [count])
就是计算值,随便用。
Vue3你想改computed:
js
const double = computed(()=>count.value*2)
double.value = 100 // ❌ 直接报错!只读!
原因
Vue设计:computed是派生状态 ,单向依赖,禁止直接修改。
想要可写computed必须手写get/set:
js
const double = computed({
get:()=>count.value*2,
set:(val)=>count.value=val/2
})
React开发者完全不习惯,天天报错。
坑9:组合式自定义Hook作用域混乱,比React更容易内存泄漏
React自定义Hook
每次组件渲染重新执行,独立作用域,销毁自动释放。
Vue3组合式函数
setup只执行一次 ,变量共享闭包作用域。
你抽离的公共逻辑:定时器、事件监听、接口订阅
组件销毁不会自动清理,必须手动onUnmounted销毁。
结果:
React写习惯不清理也没事,Vue3不清理直接内存泄漏。
坑10:v-if 会销毁重建响应式,数据莫名其妙重置
React条件渲染 && 只是不渲染,状态保留。
Vue3 v-if:条件不满足直接销毁整个代理对象。
弹窗打开→关闭→再打开:
ref/reactive全部重置初始化,数据消失。
React开发者完全不懂这个机制,疯狂debug。
坑11:失去this,props/emit写法大变,思维断层
Vue2:this.emit、this.emit、this.emit、this.router 一切挂this
React:无this,传函数回调
Vue3 setup:无this,props是只读代理,emit是函数
js
const props = defineProps()
const emit = defineEmits()
你同时懂React+Vue2,过来Vue3会三套语法混乱。
坑12:响应式嵌套、ref/unref/isRef 判断,规则极其繁琐
React:一种状态,一种规则。
Vue3:
ref / reactive / toRef / toRefs / unref / isRef / shallowRef / shallowReactive
8种响应式API,适用场景全部不同。
写复杂业务越写越乱,比React Hooks复杂得多。
终极总结(面试满分口述版)
Vue3组合式API语法看似和React Hooks相似,但底层响应式模型完全对立 :
React是不可变快照闭包模型,Vue3是Proxy可变响应式模型。
主要坑点:
- reactive解构会丢失响应式,必须toRefs
- reactive不能整体替换赋值,只能改内部属性
- ref必须.value访问,极易遗忘
- watch监听reactive属性必须传入getter函数,默认深度监听
- setup执行时机极早,无法直接获取DOM
- 闭包取值逻辑与React完全相反
- computed默认只读,不可直接修改
- 自定义组合式Hook作用域共享,易内存泄漏
- v-if会销毁重建响应式导致数据重置
- 响应式API种类繁多,规则繁琐易混淆
对于习惯React18不可变+Hooks思维的开发者,思维冲突极强,上手难度远大于Vue2选项式API,这也是为什么很多React转Vue优先选Vue2而不是Vue3。