生态对照与迁移清单
Vue vs React 心智模型对照 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
框架对照的最后一层是生态与工程实践。这一节把路由、状态、表单、请求、测试、全栈框架一一对应,并给出一份「从 React 项目迁移到 Vue」的检查清单——不是逐行翻译代码,而是找出「模型不对应」的地方。
问题场景
公司要把一个 React 项目迁到 Vue(或你接手了一个 Vue 项目但要和 React 团队协作)。逐文件翻译的结果往往是:所有东西都能跑,但没有一处写得像 Vue——用 watch 模拟 useEffect、用 provide 模拟 Context 再套一层自定义 hook、用 ref 包一切然后到处 .value。迁移的难点不是语法,是找到「等价物」和「不等价物」。
核心解释
生态对应表(等价物):
| 能力 | React | Vue 3 |
|---|---|---|
| 路由 | React Router | Vue Router |
| 全局状态 | Zustand / Redux Toolkit | Pinia |
| 服务端数据 | TanStack Query | TanStack Query(Vue 版)/ useFetch |
| 表单 | React Hook Form | VeeValidate / 手写(V06) |
| 组件库 | shadcn/ui、MUI、Ant Design | Element Plus、Naive UI、Ant Design Vue |
| 样式方案 | CSS Modules、Tailwind、styled-components | scoped CSS、Tailwind、UnoCSS |
| 测试 | Vitest/Jest + Testing Library | Vitest + Vue Test Utils |
| 全栈框架 | Next.js | Nuxt 3 |
| 动画 | Framer Motion | Vue Transition(内置)/ @vueuse/motion |
不等价物(迁移时最容易照搬错的):
- useEffect ≠ watch。useEffect 绑定「渲染周期」,watch 绑定「数据源」。React 里「挂载时执行一次」用
useEffect(..., []),Vue 里直接写在 setup 或 onMounted,不需要 watch。 - Context ≠ provide/inject 的完整替代。React Context 常配合 reducer 做全局状态;Vue 里全局状态应该用 Pinia,provide/inject 只做「环境注入」。
- useMemo 没有对应物。Vue 的 computed 自动缓存;普通计算在模板里就是每次渲染重算——但 Vue 的渲染范围小,重算成本通常可接受。
- 受控组件 vs v-model。React 的表单是「值 + onChange」显式受控;Vue 的 v-model 是语法糖,自定义组件要按
modelValue+update:modelValue契约实现(V04)。 - key 的语义相同,陷阱相同。两边都是「列表 diff 的身份」,index 当 key 都会串状态(V07)。
迁移检查清单(按顺序执行):
- 先迁「无状态展示组件」:props 一一对应,模板替换 JSX,先跑通样式。
- 再迁「状态逻辑」:useState → ref/reactive;useReducer → Pinia action 或 composable。
- 逐个处理 useEffect:挂载初始化 → onMounted;数据依赖 → watch/computed;清理 → onUnmounted/onCleanup。
- 路由与全局状态整块替换:React Router → Vue Router(路由表结构几乎一一对应);Redux/Zustand → Pinia(按领域拆,V11)。
- 请求层抽成 useFetch(V13),不要在组件里散落 fetch。
- 补测试:React Testing Library 的查询思路可直接迁移到 Vue Test Utils(getByRole → get('[role=...]'),V16)。
- 最后统一代码风格:ESLint + Prettier + 组合式 API 规范(V17)。
反向迁移(Vue → React)同样适用:把「自动追踪」翻译成「显式依赖数组」,把「可变更新」翻译成「不可变替换」,把「computed」翻译成「渲染内计算 + useMemo(按需)」。
示例拆解
一个典型迁移错误与修正。原 React 代码用 useEffect 同步两个状态:
// React:派生状态用 useMemo,不是 useEffect
const fullName = useMemo(() => `${first} ${last}`, [first, last]);
错误迁移(照搬成 watch):
// 错误:用 watch 维护派生值
const fullName = ref("");
watch([first, last], ([f, l]) => {
fullName.value = `${f} ${l}`;
});
正确迁移(识别出这是派生状态):
const fullName = computed(() => `${first.value} ${last.value}`);
这个例子说明迁移的核心动作:先判断原代码在 React 里承担什么职责,再找 Vue 里承担同一职责的原语,而不是找「名字最像的 API」。useEffect 在 React 里同时承担「副作用」和「派生同步」两种职责,迁移时必须拆开:副作用 → watch,派生 → computed。
动手任务
- 找一个你写过的 React 组件,按检查清单迁移到 Vue,记录每一步遇到的问题。
- 列出三个「React 有、Vue 没有直接对应」的 API,写出你在 Vue 里的替代方案。
- 反过来:把 V18 的看板 store 用 React(Zustand 或 useReducer)重写一版,对比代码量。
常见错误
- 逐行翻译:能跑但没有 Vue 味,维护成本高。先理解职责再选原语。
- 用 provide/inject 做全局状态:缺少集中管理与调试能力。用 Pinia。
- 把 computed 写成 watch:派生值用派生 API,副作用才用 watch。
- 迁移时改了业务逻辑:迁移应该是「等价重写」,行为变化单独提交,便于定位回归。
检查与复盘
- 能默写生态对应表里至少八行
- 能解释 useEffect 与 watch 的职责差异
- 能按检查清单完成一个小项目的迁移并记录问题
参考资料
- Vue 官方文档「与 React 的对比」:官方视角的差异清单
- React 官方文档「你可能不需要 Effect」:与 Vue computed 的对照阅读
读完这一课了?
标记完成后,工作台会安排接下来的学习。