Vue3 转 React:18 项对照表 + 5 个必踩的坑(附 7 天上手路线)

最近在换工作,把 BOSS 直聘和其它招聘软件刷了一遍,发现一件事:招前端的岗位,十有八九写的是 React。

而我写了三年 Vue3,拿得出手的项目经验也基本都是 Vue 的------经验够,技术栈不对,就这么卡住了。

所以不是"React 更流行所以我学",是很现实的原因------岗位在那儿,我得过去。好消息是,你不是从零学,你只是换一套语法,把脑子里的心智模型平移过去。

有人会说现在 AI 都能写代码了,还学这些干嘛。我的答案是:AI 写的代码,总得有人看得懂。

这篇文章不教你 React 基础。它假设你懂 Vue3 的 ref、computed、watch,只做一件事:把你已经会的东西,逐项映射到 React。

通篇是一张对照表。


先说结论:映射关系

Vue3 概念 React 对应 相似度
ref / reactive useState 高
computed useMemo 高
watch / watchEffect useEffect 中(差异最大)
模板 ref 引用 DOM useRef 中
vue-router useRoute react-router useParams 高
class="x" className="x" 完全等价

记住一条主线:Vue 帮你自动追踪依赖,React 要你手动声明依赖。这是后面所有差异的根源。


1. 状态:useState ≈ ref

Vue 里你这么写:

js 复制代码
const count = ref(0)
count.value = 1

React 里是:

js 复制代码
const [count, setCount] = useState(0)
setCount(count + 1)

对照:

维度 Vue3 React
定义 const count = ref(0) const [count, setCount] = useState(0)
读值 count.value 直接用 count
改值 count.value = 1 setCount(count + 1)
触发更新 改 .value 自动 调 setXxx 才更新

关键差别 :React 没有 .value。count 就是值本身,setCount 是唯一能改它的函数。你改了普通变量不会刷新界面------这是新手最常见的坑。


2. 副作用:useEffect vs watch / watchEffect(重点,差异最大)

2.1 React 基础用法

jsx 复制代码
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2.2 两类 effect:留没留下"活物"

判断一个 effect 要不要清理,问自己一句:"它执行完,往外面留下了活物吗?"

活物指:监听器、定时器、订阅、实例、请求。

  • 无需清除的 effect :执行完就结束,不留下东西。比如上面改 document.title,跑完就没了。
  • 需要清除的 effect:留下了订阅/监听,组件卸载时要手动断开,否则内存泄漏。
jsx 复制代码
useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});

注意那个 return 的函数------它就是清理函数,组件卸载时 React 会调用它。

2.3 什么写法会导致无限循环

这是 React 新手掉得最狠的坑。先看懂本质,再记修复。

无限循环的本质 = 三个环节连成闭环:

  1. effect 执行:渲染之后自动跑
  2. setState 状态更新:没有这一步,链就断了,安全
  3. 重新渲染:组件函数再跑一遍,渲染后检查依赖:变了 → 回到①;没变 → 停

无限循环 = 闭环完整。修复思路只有一种:断掉其中一环。

最常见的三种成因:

成因 说明 断环方法
无依赖数组 + setState effect 每次渲染都跑,一跑就 setState,又渲染,又跑... 加依赖数组 []
依赖引用类型,每次渲染新建 {a:1} 每次都是新引用,Object.is 永远不等 依赖原始值,或 useMemo 稳定引用
setState 产生的新引用又进依赖 自己造的依赖自己改 用函数式更新 setX(x => x+1)

2.4 Vue 这边:watch vs watchEffect

js 复制代码
// watch:只追踪你明确写的数据源,依赖精确可控
watch(id, (newId, oldId) => {
  // 仅 id 变才触发
})

// watchEffect:同步执行时自动追踪访问到的所有响应式属性
watchEffect(() => {
  // 访问了谁就追踪谁,更方便但关系不那么明显
})
维度 watch watchEffect
依赖来源 手动指定数据源 自动追踪访问到的响应式属性
触发时机 数据源确实改变才触发 同步执行期间自动收集
精确度 高,能精确控制 方便但依赖关系有时不明

2.5 Vue 清理副作用(3.5+)

js 复制代码
import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const controller = new AbortController()
  fetch(`/api/${newId}`, { signal: controller.signal }).then(() => { /* ... */ })
  onWatcherCleanup(() => {
    controller.abort() // 终止过期请求
  })
})

老写法用回调的第三个参数:

js 复制代码
watch(id, (newId, oldId, onCleanup) => {
  onCleanup(() => { /* 清理逻辑 */ })
})
watchEffect((onCleanup) => {
  onCleanup(() => { /* 清理逻辑 */ })
})

对照小结 :Vue 的 watch/watchEffect 更"聪明"(自动追踪),React 的 useEffect 更"笨"但更可控(依赖要你手写)。这是两种哲学,不是谁更好。


3. 派生数据:useMemo ≈ computed

Vue 的 computed:

js 复制代码
const double = computed(() => count.value * 2)

React 的 useMemo:

js 复制代码
const double = useMemo(() => count * 2, [count])

从三个维度对照:

维度 Vue computed React useMemo
依赖怎么来 自动追踪 手动声明数组 [count]
依赖变了 重算 重算
用来稳定引用 基本不需要 经常需要(React 靠 Object.is 比引用)

第三行是重点。React 里 useMemo 不止为了"算得快",更常为了"引用稳定" 。因为 React 默认用 Object.is 比较前后值,你每次渲染 new 一个对象/数组/函数,引用就变了,会触发不该触发的重渲染或 effect。这时候 useMemo(或 useCallback)就是用来把引用钉住的。

Vue 不需要这一步,因为响应式系统自己管引用。


4. useCallback 就是 useMemo 的语法糖

记住一句话:

js 复制代码
useCallback(fn, deps)
// 等价于
useMemo(() => fn, deps)

它专门用来稳定函数 的引用。场景:你要把一个函数作为 prop 传给子组件,又不想每次渲染都造个新函数导致子组件无谓重渲染------用 useCallback 包一下。


5. useRef:跨渲染一直存在的盒子

js 复制代码
const timer = useRef(null)
timer.current = setInterval(...)

useRef 是一个跨越渲染一直存在的盒子 。你改 timer.current,界面不会重新渲染。

这里要纠一个常见误解:useRef 不是 Vue 的 reactive。

  • reactive 改了值 → 界面自动更新(响应式)
  • useRef 改了值 → 界面不动,它只是个能活过多次渲染的容器

useRef 真正用在两处:① 存定时器/订阅这种"不想触发渲染的变量";② 模板里拿 DOM 节点(等价于 Vue 的模板 ref)。


6. CSS:class → className(附一个真实踩坑)

Vue:

html 复制代码
<div class="card"></div>

React:

jsx 复制代码
<div className="card"></div>

就这一个词不一样。但有个真实坑我替你踩过:

在 Vite + React + TS 项目里,如果你写 import './taskListPage.css',编辑器可能报:

lua 复制代码
src/pages/TaskListPage.tsx:4:39 - error TS2882:
Cannot find module or type declarations for side-effect import of './taskListPage.css'.

原因:TS 不认识 CSS 这种"副作用导入"。修法是新建一个 src/vite-env.d.ts,放一行:

ts 复制代码
/// <reference types="vite/client" />

Vite 的类型声明就位后,TS 就知道 .css 导入是合法的了。这个文件 Vite 官方脚手架其实自带,自己建项目时容易漏。


7. 路由:useParams vs useRoute

Vue:

js 复制代码
import { useRoute } from 'vue-router'
const route = useRoute()
route.params.id
route.query.tab

React(react-router v6/v7):

jsx 复制代码
import { useParams, Link } from 'react-router-dom'

// 跳转:Link 只改 URL
<Link to={`/tasks/${taskNo}`}>详情</Link>

// 读取:在目标页用 useParams 取
const { taskNo } = useParams()

一个很多人绕不过去的弯 :<Link to> 负责"改 URL",useParams() 负责"读参数",两者是解耦 的。不是"用了 Link 跳转才配用 useParams"------任何匹配到 :taskNo 这个路由的页面,都能用 useParams 拿到它。跳转和取参是两条独立的线。


18 项速查表(收尾)

把上面拆细,就是你要的 18 项对照:

# 概念 Vue3 React 关键差异
1 定义状态 ref() useState() React 返回 [值, 改值函数]
2 读状态 x.value 直接用 x React 无 .value
3 改状态 x.value = setX() React 只能靠 setter
4 基础副作用 watchEffect useEffect React 依赖手写
5 不清除的 effect 执行完即止 useEffect 无 return 同理
6 需清除的 effect onCleanup return () => {...} 清理函数写法不同
7 无限循环成因 极少 三环闭环 React 高频坑
8 无限循环修复 --- 断环三法 空依赖/原始值/函数式更新
9 精确依赖 watch useEffect([deps]) 都手动
10 自动依赖 watchEffect (无原生对应) React 需手动
11 Vue 清理 3.5+ onWatcherCleanup --- Vue 专属
12 派生值 computed useMemo 都缓存
13 依赖来源 自动追踪 手动声明 核心差异
14 稳定引用需求 基本不需 经常需要 React Object.is
15 稳定函数 --- useCallback 语法糖
16 跨渲染容器 --- useRef ≠ reactive
17 DOM 引用 模板 ref useRef 同用途
18 样式类名 class className 仅一词之差

7 天上手路线(可以直接照抄)

这是我自己的排期,每天大概 1-2 小时。顺序和节奏你都可以按自己的情况调,不用照搬。

天 内容 对应上表
Day 1 心智切换:JSX + useState 1--3
Day 2 useEffect 基础 + 清理 4--6
Day 3 无限循环专题 7--8
Day 4 computed ↔ useMemo 12--14
Day 5 useCallback + useRef 15--17
Day 6 路由 Link / useParams 18 之外
Day 7 综合:抄一个自己的小页 全部

Vue 和 React 不是两个语言,是两种"谁来管理依赖"的哲学。Vue 替你记,React 让你写。你 Vue3 的老底子在,平移过去最多一周。真正要小心的只有一处:React 的依赖要你亲手声明,漏了就无限循环,多了就白渲染。

把这张表存好。下次卡住,回来查第几行。


写在最后:这份对照表是我自己转 React 时边学边记的。接下来转型路上真正踩过的坑------Node、TS、部署这些,我还会继续写,学到哪儿写到哪儿,不按固定顺序。如果你也是从 Vue 过来的,欢迎在评论区说说你卡在哪儿,我可能正好也踩过。

另外也想问一句:AI 都能写 React 了,你觉得还有必要系统学一遍吗? 想听听大家的答案。

相关推荐
广州华水科技1 小时前
单北斗GNSS位移监测系统的应用与维护解析
前端
梦曦i2 小时前
uni-storage 0.1.0 发布:TTL过期管理+多实例隔离
前端·storage·uni-app x
猫头_3 小时前
AI 流式传输工程指南:模型吐出半截 JSON,前端如何边收边渲染?
前端·javascript·llm
console.log('npc')3 小时前
第 1 章 项目概述与环境搭建
前端·人工智能·大模型
定栓3 小时前
Webpack迁到Vite第七节(二):全家桶移除 — Babel 全家桶深度分析
前端·webpack·node.js
IT_陈寒3 小时前
React组件莫名重渲染?你可能忘了这个小细节
前端·人工智能·后端
鶴哥只手遮天4 小时前
React 19.3.1 源码深度剖析-第 12 章(下)RSC 与 Flight 协议
前端·react.js·开源
deli0074 小时前
TCP 三次握手到底发几个包?丢包重传动画实测一遍
前端
盟接之桥4 小时前
盟接之桥谈线束数字化-按单入按单出:线束企业库存管理的破局之道
大数据·前端·网络·数据库·人工智能·制造