ChenZhen 搜索
首页 标签 归档 留言板 友链 ChatGPT 提示库 AI工具导航网 🚇开往 关于我

Vue3学习React学习文档记录

本文为Vue开发者提供React核心概念的系统性对照学习指南。首先通过对比框架定位、响应式机制等维度阐明React"UI=函数(state)"的哲学本质,重点解析了React组件作为普通函数与Vue组件的差异,详细对比了状态管理(useState vs ref)、模板语法(JSX vs template)、事件处理等核心概念。特别深入讲解了useEffect这一重要Hook,通过生命周期对照表展示其如何替代Vue的onMounted/watch等功能,并强调清理函数的必要性。最后指出Re

ChenZhen 2026-03-01T15:36:55

Vue3学习React学习文档记录

一、React 前言总结

学习路线(我给你规划好)

🚀 第 1 阶段(必须)

  • JSX
  • useState
  • 事件
  • 列表 / 条件渲染

🚀 第 2 阶段(核心)

  • useEffect(≈ watch + 生命周期)
  • Props / 父子通信
  • 受控组件

🚀 第 3 阶段(工程能力)

  • 自定义 Hook
  • Context
  • React Router
  • React Query / Zustand

1、先给你一个大局观(非常重要)

1️⃣ React 和 Vue3 的本质区别(一句话版)

Vue:框架(帮你规定好一切) React:库(你用 JavaScript 自己组织一切)

维度Vue3React
定位完整框架UI 库
响应式自动(Proxy)手动(useState)
模板templateJSX(JS 写 UI)
数据流双向绑定单向数据流
思想MVVM函数式 UI

👉 React 的核心哲学只有一句话:

UI = f(state) 视图是状态的函数


2、React 项目怎么起?(你先不用纠结)

Vue3(你熟的)

npm create vue@latest

React(现在主流)

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

你现在只需要记住

  • React 项目 100% 写 JS
  • 没有 .vue 文件
  • 所有 UI 都是 JSX

3、React 最核心的东西:组件

1️⃣ Vue3 组件(你非常熟)

<script setup>
const count = ref(0)
</script>

<template>
  <button @click="count++">
    {{ count }}
  </button>
</template>

2️⃣ React 组件(第一眼会不适应)

import { useState } from 'react'

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

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  )
}

export default App

4、重点对照理解(一定要看)

1️⃣ React 组件 = 普通函数

function App() {
  return <div>Hello</div>
}

👉 没有 data / methods / computed / watch


2️⃣ 状态:ref / reactive → useState

Vue3React
ref(0)useState(0)
.valuesetter
自动追踪手动 set

Vue

const count = ref(0)
count.value++

React

const [count, setCount] = useState(0)
setCount(count + 1)

声明一个状态 count,初始值是 0,并拿到一个修改它的方法 setCount

🚨 React 铁律

不能直接改 state

❌ count++
❌ state.xxx = 1

3️⃣ 模板 vs JSX(React 最重要的认知坎)

Vue template

<div>{{ count }}</div>

React JSX

<div>{count}</div>

👉 JSX 不是模板,是 JS 表达式 你可以直接写逻辑:

{count > 5 ? '大于5' : '小于等于5'}

5、事件绑定(Vue 同学最容易懵)

Vue

<button @click="add">+</button>

React

<button onClick={add}>+</button>

📌 注意三点:

  1. 驼峰命名onClick
  2. 传函数,不是字符串
  3. 没有 this

6、条件 & 列表渲染(Vue → React 对照)

条件渲染

Vue

<div v-if="show">显示</div>

React

{show && <div>显示</div>}

列表渲染(非常重要)

Vue

<li v-for="item in list" :key="item.id">
  {{ item.name }}
</li>

React

{list.map(item => (
  <li key={item.id}>{item.name}</li>
))}

👉 React 没有指令,只有 JS


7、React 最容易踩的 3 个坑(提前帮你避)

❌ 1. 想用双向绑定

React 没有 v-model

👉 你要写成:

<input
  value={value}
  onChange={e => setValue(e.target.value)}
/>

❌ 2. 想在 render 里写副作用

❌ 发请求
❌ setState
❌ 操作 DOM

👉 要用 useEffect


太棒了,选择 useEffect 作为切入点非常明智。它是 React 中最强大但也最容易让 Vue 开发者感到困惑的地方。

在 Vue3 中,你有明确的生命周期钩子(onMounted, onUpdated, onUnmounted)和 watch。但在 React 中,这些功能全部由 useEffect 这一个 Hook 承包了。


二、了解 React 的“生命周期”(useEffect)

1. 核心心智模型:同步而非生命周期

  • Vue3 的思路:组件到了某个阶段(挂载了、更新了),执行某个函数。
  • React 的思路副作用(Side Effect)与依赖(Dependencies)的同步useEffect 的意思是:“当这些依赖项变化时,请重新运行这段逻辑。”

useEffect 的语法结构

useEffect(() => {
  // 1. 这里是副作用逻辑 (例如 API 请求、订阅)

  return () => {
    // 2. 这里是清理逻辑 (Cleanup,相当于 onUnmounted)
  };
}, [dependencies]); // 3. 依赖数组


2. Vue3 vs React 模式对照表

我们可以通过调整 useEffect 的第二个参数(依赖数组),来实现 Vue 中的不同钩子功能:

Vue3 生命周期 / 监听React useEffect 写法说明
onMounteduseEffect(() => { ... }, [])空数组。只在组件初次渲染后执行一次。
watch(source)useEffect(() => { ... }, [source])有值数组。当 source 变化时执行。
onUpdateduseEffect(() => { ... })不传数组。每次组件渲染/更新后都执行(慎用)。
onUnmounteduseEffect(() => { return () => { ... } }, [])返回一个函数。该函数会在组件销毁前调用。

3. 重点突破:清理函数 (Cleanup)

在 Vue 中,你可能在 onMounted 里开启定时器,在 onUnmounted 里关闭。 在 React 中,这属于同一个逻辑块

useEffect(() => {
  const timer = setInterval(() => {
    console.log('计时中...');
  }, 1000);

  // 清理函数:组件卸载 或 下次 Effect 执行前执行
  return () => {
    clearInterval(timer);
    console.log('计时器已清除');
  };
}, []); // 只在挂载时开启,卸载时关闭


4. 常见误区:为什么我的 useEffect 执行了两次?

你可能会发现,在开发环境下,useEffect 即使数组为空也会执行两次。

  • 原因:React 的 Strict Mode (严格模式)。它会故意模拟“挂载 -> 卸载 -> 挂载”的过程,用来帮你检查是否有遗漏的“清理函数”(比如忘记关掉定时器或 WebSocket)。
  • Vue 开发者视角:这在生产环境不会发生,不用担心。

5. 实战对比:请求数据

让我们看一个最常见的业务场景。

Vue3 做法

const data = ref(null);
onMounted(async () => {
  data.value = await fetchData(props.id);
});
watch(() => props.id, async (newId) => {
  data.value = await fetchData(newId);
});

React 做法 (更简洁)

const [data, setData] = useState(null);

useEffect(() => {
  let isIgnore = false;
  
  fetchData(id).then(res => {
    if (!isIgnore) setData(res);
  });

  return () => { isIgnore = true; }; // 防止竞态条件
}, [id]); // 只要 id 变了,就重新获取数据,一行搞定 watch + mounted


三、受控组件

在 Vue3 中,我们习惯了用 v-model 走天下。它是一个“语法糖”,帮我们自动处理了变量绑定和事件监听。

在 React 中,没有 v-model**。你需要手动拆解这个过程,这就是所谓的“受控组件” (Controlled Components)**。


1. 核心概念:数据双向绑定的拆解

在 React 的哲学里,“数据流是单向的”

  • **Vue 的 v-model**:数据 <==> 视图(自动双向)。
  • React 的受控组件:数据 ==> 视图,视图 --(通过事件)--> 修改数据(手动循环)。

2. 代码实现对比

让我们实现一个简单的输入框,同步显示用户输入的内容。

Vue3 (v-model)

<script setup>
import { ref } from 'vue'
const text = ref('')
</script>

<template>
  <input v-model="text" />
  <p>你输入了: {{ text }}</p>
</template>

React (受控组件)

你需要做两件事:

  1. value 绑定到状态。
  2. 绑定 onChange 事件来手动更新状态。
import React, { useState } from 'react';

function InputExample() {
  const [text, setText] = useState('');

  const handleChange = (e) => {
    // e.target.value 就是当前输入框的值
    setText(e.target.value);
  };

  return (
    <div>
      <input 
        value={text}           // 1. 绑定值 (类似 :value)
        onChange={handleChange} // 2. 监听变化 (类似 @input)
      />
      <p>你输入了: {text}</p>
    </div>
  );
}


3. 为什么要叫“受控”?

之所以叫“受控”,是因为这个表单元素的值完全由 React 的 State 控制

  • 如果你只写 value={text} 而不写 onChange,你会发现输入框锁死了,怎么敲键盘都没反应。
  • 只有当你通过 setText 更新了状态,React 重新渲染组件,输入框的值才会改变。

这虽然比 Vue 多写了两行代码,但它的好处是:你可以完全掌控输入。例如,你想让用户只能输入数字:

const handleChange = (e) => {
  const val = e.target.value;
  // 如果不是数字,直接不更新状态,输入框也就不会有变化
  if (!isNaN(val)) {
    setText(val);
  }
};


4. 多个输入的处理技巧

在 Vue 中,你会写很多个 v-model。在 React 中,如果你有 10 个输入框,难道要写 10 个 handleChange 吗?

Vue 开发者的小技巧:利用对象的计算属性键名。

const [form, setForm] = useState({ name: '', age: '' });

const handleChange = (e) => {
  const { name, value } = e.target;
  // 注意:React 修改对象必须传入一个全新的对象(不可变性)
  setForm({
    ...form,      // 复制旧数据
    [name]: value // 修改当前字段
  });
};

// 渲染时
<input name="name" value={form.name} onChange={handleChange} />
<input name="age" value={form.age} onChange={handleChange} />


5. 什么时候用“非受控组件”?

偶尔你也会怀念原生 HTML 的操作,不想把每个输入都绑在状态上,React 提供了 useRef

  • useRef 类似于 Vue3 的 const myRef = ref(null)
  • 你可以通过 ref 直接从 DOM 节点获取值,而不需要 useState。这被称为**“非受控组件”**。

总结

  • Vue: v-model 是自动驾驶,方便快捷。
  • React: 受控组件是手动挡,虽然需要你亲自操作(value + onChange),但在处理复杂逻辑(格式化、验证)时非常透明和灵活。

你已经掌握了状态、副作用和表单。接下来,要不要了解一下 React 中最让初学者头疼的“组件重新渲染”逻辑?或者我们直接聊聊如何处理组件间的状态共享(类似 Vuex/Pinia 的方案)?

四、JSX

既然你已经了解了受控组件,那我们必须深入聊聊 JSX。这是 React 的灵魂,也是很多 Vue 开发者一开始最不习惯的地方。

在 Vue 中,HTML、CSS 和 JS 是“各回各家,各找各妈”(SFC 模式)。而在 React 中,JSX 让我们直接在 JS 里写 HTML。


1. JSX 到底是什么?

JSX 看起来像 HTML,但它既不是字符串,也不是真正的 HTML。它是 JavaScript 的语法扩展

当你写下 <h1>Hello</h1> 时,Babel 等编译器会把它转换成一个普通的 JS 函数调用:

// 你写的
const element = <h1>Hello</h1>;

// 编译器转成的 (React 17+ 之后)
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("h1", { children: "Hello" });

结论:JSX 本质上就是创建对象的表达式。这意味着你可以把 UI 赋值给变量、当作参数传递、或者从函数中返回。


2. Vue Template 与 JSX 的规则差异

虽然长得像,但 JSX 有几个“硬性规定”,这和 Vue 的模板语法不同:

① 必须有一个根元素 (Fragments)

在 Vue3 中,模板可以有多个根节点。但在 React 中,JSX 必须被包裹在一个元素内。如果你不想增加额外的 <div>,可以使用 Fragment (<>...</>)。

// ❌ 错误
return (
  <Header />
  <Content />
)

// ✅ 正确
return (
  <>
    <Header />
    <Content />
  </>
)

② 属性命名的“驼峰化”

因为 JSX 更接近 JS 而不是 HTML,所以 HTML 的属性名要改写成 JS 的属性名:

  • class ➡️ className(因为 class 是 JS 的关键字)
  • for ➡️ htmlFor
  • onclick ➡️ onClick
  • style="color: red" ➡️ style={{ color: 'red' }} (注意:这里是双大括号,外层是 JS 环境,内层是 JS 对象)

3. 在 JSX 中嵌入 JS (大括号的魔力)

在 Vue 中,你用 {{ }} 插值;在 JSX 中,你只用 **单括号 { }**大括号里可以写任何有返回值的 JS 表达式。

function Welcome() {
  const name = "张三";
  const formatName = (user) => user.firstName + user.lastName;

  return (
    <div title={name}> {/* 属性里也能用 */}
      <h1>Hello, {name}</h1>
      <p>计算结果: {1 + 1}</p>
      <p>函数调用: {formatName({firstName: '王', lastName: '小二'})}</p>
    </div>
  );
}


4. 逻辑处理:没有指令,只有 JS

这是 Vue 开发者最需要转换思维的地方:**React 没有 v-ifv-for**

条件渲染

使用 JS 的 && (与运算符) 或 ? : (三元运算符)。

{/* 相当于 v-if */}
{isLoggedIn && <LogoutButton />}

{/* 相当于 v-if / v-else */}
{isLoggedIn ? <Welcome /> : <Login />}

列表渲染

使用 JS 的 .map() 方法。记得带上 key

const items = ['苹果', '香蕉', '橘子'];

return (
  <ul>
    {items.map((item, index) => (
      <li key={index}>{item}</li>
    ))}
  </ul>
);


5. 为什么 React 选择 JSX?

Vue 的模板更“静态”,Vue 编译器可以通过分析模板做很多自动化优化(如静态提升)。 React 的 JSX 更“动态”,它给了你 JS 的完整能力

  • Vue 思路:我为你提供了一套好用的工具箱(指令),你按规矩来。
  • React 思路:我直接给你 JS 本身,你想怎么组合、怎么写逻辑,随你便。

五、事件

在 Vue3 中,我们使用 @clickv-on:click 来处理事件。在 React 中,事件处理依然遵循 “一切皆是 JS” 的原则。

掌握 React 事件,你只需要关注 3 个核心区别:命名方式传参方式、以及事件对象


1. 命名与绑定:从 @onCamelCase

  • Vue: 使用小写命名,并配合指令,例如 @click@keyup.enter
  • React: 使用 小驼峰 (camelCase) 命名,且直接传递函数引用。

对比代码:

Vue3:

<button @click="handleClick">提交</button>

React:

// 注意是 onClick,大写 C
<button onClick={handleClick}>提交</button>

注意: 在 React 中,一定要传递函数本身onClick={handleClick}),而不是函数的执行结果(onClick={handleClick()}),除非你的函数返回的也是一个函数。


2. 事件传参:没有指令,只有闭包

在 Vue 中,传参很简单:@click="say('hi')"。 在 React 中,如果你写 onClick={say('hi')},函数会在组件渲染时立即执行

要在 React 中传参,你需要写一个匿名函数(箭头函数)

function App() {
  const handleDelete = (id) => {
    console.log('删除 ID:', id);
  };

  return (
    // 使用箭头函数包装一下
    <button onClick={() => handleDelete(123)}>删除</button>
  );
}


3. 事件对象 (SyntheticEvent)

在 Vue 中,你可以通过 $event 获取原生 DOM 事件。 在 React 中,你得到的是一个 合成事件 (SyntheticEvent)

React 为了跨浏览器兼容性,把原生事件包装了一层。它的接口和原生事件(e.preventDefault(), e.stopPropagation())一模一样,但它是经过 React 优化的。

const handleChange = (e) => {
  console.log(e.target.value); // 获取输入框的值
  e.preventDefault();          // 阻止默认行为(如表单提交刷新)
};


4. 常见的事件修饰符:手动实现

Vue 提供了非常方便的修饰符,如 .stop, .prevent, .once, .enter。 React 没有任何修饰符。你需要手动在函数体里写 JS 代码。

功能Vue 写法React 写法
阻止冒泡@click.stope.stopPropagation()
阻止默认行为@submit.prevente.preventDefault()
按键过滤@keyup.enterif (e.key === 'Enter') { ... }

示例:处理回车键

const handleKeyUp = (e) => {
  if (e.key === 'Enter') {
    console.log('用户按了回车');
  }
};

<input onKeyUp={handleKeyUp} />


5. 为什么 React 没有修饰符?

这体现了 React 的设计哲学:“显式优于隐式”。 React 希望你清楚地知道代码在做什么,而不是隐藏在 HTML 指令的黑盒里。虽然多写了一行 e.preventDefault(),但代码的逻辑路径更加清晰。


老师的总结:一个经典的 React 事件处理套路

import { useState } from 'react';

function Form() {
  const [val, setVal] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault(); // 1. 手动阻止表单刷新
    alert('提交成功: ' + val);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={val} 
        onChange={(e) => setVal(e.target.value)} // 2. 闭包+受控组件
      />
      <button type="submit">提交</button>
    </form>
  );
}

六、父子通信

在 Vue3 中,父子通信靠的是 propsemit。在 React 中,逻辑更简单也更纯粹:一切皆为 Props

React 遵循 “单向数据流”:数据从父传给子;如果子要改数据,就调用父传下来的“回调函数”。


1. 父传子:传递数据

这部分和 Vue 非常像。你在父组件的标签上写属性,子组件通过函数的第一个参数 props 接收。

Vue3 做法

<Child message="Hello" :count="10" />

<script setup>
const props = defineProps(['message', 'count'])
</script>

React 做法

在 React 中,我们通常直接用 对象解构 来获取 props,这样代码最简洁。

// 子组件
function Child({ message, count }) { 
  return (
    <div>
      <p>{message}</p>
      <p>数量:{count}</p>
    </div>
  );
}

// 父组件
function Parent() {
  return <Child message="来自父组件的消息" count={100} />;
}


2. 子传父:传递回调函数

这是 Vue 开发者最需要转换思维的地方。React **没有 $emit**

  • Vue 思路:子组件通过 emit('update') “大喊”一声,父组件监听。
  • React 思路:父组件把一个函数像普通变量一样传给子组件,子组件在合适的时候调用这个函数

代码实现:

// 子组件
function Child({ onIncr }) {
  return (
    // 当点击时,直接调用父组件传来的函数
    <button onClick={() => onIncr(1)}>点我加 1</button>
  );
}

// 父组件
function Parent() {
  const [count, setCount] = useState(0);

  const handleIncr = (step) => {
    setCount(count + step);
  };

  return (
    <div>
      <h1>计数器:{count}</h1>
      {/* 像传数据一样传函数 */}
      <Child onIncr={handleIncr} />
    </div>
  );
}


3. Props 的“槽位”:Children (类似 Slot)

Vue 使用 <slot> 来传递插槽内容。React 使用一个特殊的 prop 叫做 children

Vue3 (Slot)

<MyCard>
  <p>我是插槽内容</p>
</MyCard>


React (Children)

任何夹在组件双标签中间的内容,都会被自动放入 props.children 中。

function MyCard({ children, title }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="content">
        {children} {/* 这里就是插槽内容 */}
      </div>
    </div>
  );
}

// 使用
<MyCard title="公告">
  <p>我是写在组件中间的内容,我会出现在 children 里。</p>
</MyCard>


4. Props 的只读性

和 Vue 一样,React 的 Props 是只读的。

  • 错误操作:在子组件里直接修改 props.count = 10
  • 正确操作:如果需要修改,必须调用父组件通过 props 传下来的 setCount 方法。

5. 跨级组件通信 (Provide/Inject vs Context)

如果你有很深的组件树(父 -> 子 -> 孙),一级级传 Props 会很痛苦(这叫 Props Drilling)。

  • Vue3: 使用 provideinject
  • React: 使用 Context API

七、全局状态

八、路由(Routing)

既然我们已经聊到了全局状态,那么路由(Routing)就是把所有组件串联成一个完整应用的“骨架”。

在 Vue 中,vue-router 是官方深度集成的。在 React 中,虽然没有官方出品的路由,但 React Router 是事实上的标准,几乎 99% 的项目都在用它。


1. 核心概念对比:从 router-view 到组件化路由

Vue 的路由通常是配置驱动的(在 router/index.js 里写死一张表),而 React 的路由更倾向于组件驱动

功能Vue RouterReact Router (v6+)
容器<router-view /><Outlet />
链接<router-link to="..."><Link to="...">
编程式导航router.push('/...')useNavigate()
动态参数$route.params.iduseParams()

2. 基础路由配置

在 React 中,我们通常在 App.jsx 中定义路由树。

import { BrowserRouter, Routes, Route, Link, Outlet } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>

      {/* 路由配置区域 */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        {/* 动态路由示例 */}
        <Route path="/user/:id" element={<UserDetail />} />
      </Routes>
    </BrowserRouter>
  );
}


3. 嵌套路由:从 childrenOutlet

在 Vue 中,嵌套路由会渲染在子组件的 <router-view /> 里。在 React 中,这个占位符叫 <Outlet />

// 父级路由组件
function Layout() {
  return (
    <div className="admin-layout">
      <aside>侧边栏菜单</aside>
      <main>
        {/* 相当于 Vue 的 <router-view /> */}
        <Outlet />
      </main>
    </div>
  );
}

// 路由配置
<Route path="/admin" element={<Layout />}>
  <Route path="profile" element={<Profile />} />
  <Route path="settings" element={<Settings />} />
</Route>


4. 编程式导航与参数获取

作为 Vue 开发者,你最关心的可能是:如何在 JS 逻辑里跳转页面?如何获取 URL 里的 ID?

① 跳转页面 (useNavigate)

import { useNavigate } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = () => {
    // 登录成功后跳转,相当于 router.push('/dashboard')
    navigate('/dashboard');
  };

  return <button onClick={handleLogin}>登录</button>;
}

② 获取参数 (useParams)

import { useParams } from 'react-router-dom';

function UserDetail() {
  // 假设路径是 /user/123
  const { id } = useParams(); 
  
  return <div>用户 ID: {id}</div>;
}


© 版权声明
😀😃😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘😗😚😙😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬🤥😌😔😪🤤😴😷🤒🤕🤢🤮🤧🥵🥶🥴😵🤯🤠🥳😎🤓🧐😕😟🙁☹️😮😯😲😳🥺😦😧😨😰😥😢😭😱😖😣😞😓😩😫🥱😤😡😠🤬