React18 Hooks 项目重构为 Vue3 组合式API的坑

超详细完整版: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 } // ❌ 页面不更新!彻底失效!

原因

  1. user 只是一个变量名,指向Proxy代理地址
  2. 你重新赋值 user = 新对象
    → 变量指向了普通新对象
    → 不再指向原来的Proxy代理
  3. 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

更坑的是:

  1. reactive内部的ref会自动解包,不用.value
  2. 数组里的ref不会自动解包,必须.value
  3. computed里自动解包
  4. 函数返回里不解包

你从React过来,完全没有这套规则,写一段错一段。


坑5:watch 监听逻辑 ≠ useEffect,完全反向(最容易逻辑写崩)

现象

React useEffect:

js 复制代码
useEffect(()=>{
  // 依赖变化执行
}, [age])

清晰、简单、浅层监听。

Vue3你照搬写:

js 复制代码
const user = reactive({ age:18 })
watch(user.age, ()=>{ console.log('变化') }) // ❌ 完全不监听!

原因底层

Vue3 watch 分两种:

  1. 监听ref基本值:直接监听
  2. 监听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可变响应式模型。

主要坑点:

  1. reactive解构会丢失响应式,必须toRefs
  2. reactive不能整体替换赋值,只能改内部属性
  3. ref必须.value访问,极易遗忘
  4. watch监听reactive属性必须传入getter函数,默认深度监听
  5. setup执行时机极早,无法直接获取DOM
  6. 闭包取值逻辑与React完全相反
  7. computed默认只读,不可直接修改
  8. 自定义组合式Hook作用域共享,易内存泄漏
  9. v-if会销毁重建响应式导致数据重置
  10. 响应式API种类繁多,规则繁琐易混淆

对于习惯React18不可变+Hooks思维的开发者,思维冲突极强,上手难度远大于Vue2选项式API,这也是为什么很多React转Vue优先选Vue2而不是Vue3。

相关推荐
雕刻刀2 小时前
服务器模拟断网
linux·服务器·前端
zs宝来了2 小时前
Vite 构建原理:ESBuild 与模块热更新
前端·javascript·框架
2301_814809862 小时前
实战分享Flutter Web 开发:解决跨域(CORS)问题的终极指南
前端·flutter
ayqy贾杰3 小时前
GPT-5.5+Codex全自动搓出macOS游戏,创作链路首次真正连续
前端·面试·游戏开发
英俊潇洒美少年5 小时前
Vue2/Vue3 vue-i18n完整改造流程(异步懒加载+后端接口请求)
前端·javascript·vue.js
空中海11 小时前
第七章:vue工程化与构建工具
前端·javascript·vue.js
zhensherlock11 小时前
Protocol Launcher 系列:Trello 看板管理的协议自动化
前端·javascript·typescript·node.js·自动化·github·js
zhuà!11 小时前
element的el-form提交校验没反应问题
前端·elementui
龙猫里的小梅啊11 小时前
CSS(一)CSS基础语法与样式引入
前端·css