React 入门指南:新手必读
前言
欢迎来到 React 的世界!如果你是前端开发的新手,或者你已经熟悉了 HTML、CSS 和 JavaScript,但想要学习当下最流行、最强大的前端库之一来构建现代用户界面,那么你来对地方了。
React 是由 Meta (原 Facebook) 开发和维护的一个用于构建用户界面的 JavaScript 库。它不是一个完整的框架(比如 Angular 或 Vue),它主要负责应用中的视图(View)层。React 以其声明式、组件化和高效的特性,迅速成为了前端开发领域的主流选择。
学习 React 不仅能让你掌握构建复杂单页应用(SPA)的能力,还能为你打开通往 React Native(移动应用开发)和 Next.js(全栈 React 框架)等更广阔领域的大门。
本指南旨在为完全没有 React 经验的新手提供一个清晰、全面的入门路径。我们将从环境搭建开始,逐步深入到 React 的核心概念:组件、JSX、Props、State、事件处理以及一些常用的 Hook。通过本指南的学习,你将能够搭建一个基本的 React 应用,理解 React 的基本工作原理,并为进一步深入学习打下坚实的基础。
阅读本指南前,你需要具备以下基础知识:
- HTML、CSS 的基本知识。
- JavaScript 的基本知识,特别是 ES6+ 的新特性(如箭头函数、
let
/const
、模块导入导出等)。
准备好了吗?让我们开始 React 之旅吧!
目录
- 为什么选择 React?
- 声明式编程
- 组件化
- 高效的虚拟 DOM
- 庞大的社区和生态
- 准备环境与创建第一个 React 应用
- 安装 Node.js
- 选择项目构建工具 (Vite)
- 使用 Vite 创建 React 项目
- 项目结构概述
- 运行你的第一个 React 应用
- React 的核心概念 – 组件
- 什么是组件?
- 函数式组件 vs 类组件 (简述)
- 创建第一个函数式组件
- 渲染组件
- 组件的组合
- JSX 语法
- JSX 是什么?
- JSX 的基本规则
- 在 JSX 中嵌入 JavaScript 表达式
- 条件渲染 (JSX 中的逻辑判断)
- 列表渲染 (渲染多个元素)
- 样式处理 (CSS)
- Props – 组件通信的桥梁
- 什么是 Props?
- 传递 Props
- 在组件中接收和使用 Props
- Props 的单向数据流
- 默认 Props
- State – 管理组件内部数据
- 什么是 State?
- 使用
useState
Hook - 声明 State 变量
- 更新 State
- State 更新的异步性
- 基于上一个 State 更新
- 一个 State 示例:简单的计数器
- 事件处理
- 在 React 中处理事件
- 事件命名和传递
- 访问事件对象
- 阻止默认行为
- 条件渲染与列表渲染深入
- 不同的条件渲染方法
- 列表渲染的 Key 属性 (重点!)
- Hooks 简介 (以
useEffect
为例)- 什么是 Hook?
useEffect
Hook 的作用- 清理副作用
- 依赖项数组
- 接下来做什么?
- 学习路由
- 学习状态管理
- 学习数据获取
- 深入学习 Hooks
- 构建更多项目
- 总结
1. 为什么选择 React?
在开始学习 React 之前,了解为什么它如此受欢迎是很重要的。
声明式编程
传统的命令式编程,你需要一步步告诉计算机“怎么做”。比如操作 DOM,你需要先找到元素,然后修改它的属性、添加子元素等等。
而声明式编程,你只需要告诉计算机“要做什么”,至于“怎么做”,由库或框架来完成。在 React 中,你描述的是应用在不同状态下应该呈现的样子,React 会负责高效地更新 UI,使其与当前状态同步。这使得代码更易于理解和维护。
想象一下,你不是告诉画家“先画一个圆,然后在圆的右边画一个正方形,填充红色…”,而是直接说“请画一幅美丽的夕阳图”。React 就是那个能理解你意图并帮你完成复杂绘画的智能画家。
组件化
React 的核心思想是组件化。一个复杂的 UI 可以被拆分成许多独立的、可重用的组件。每个组件只负责渲染自身的一部分 UI,并且可以有自己的逻辑和状态。
这就像搭乐高积木一样,你可以用各种小积木(组件)拼出任何你想要的大型结构(应用)。这种模块化的开发方式大大提高了代码的可重用性、可维护性和开发效率。
高效的虚拟 DOM (Virtual DOM)
直接操作浏览器 DOM 是非常耗费性能的,尤其是在大型复杂应用中。React 引入了虚拟 DOM 的概念。
虚拟 DOM 是一个 JavaScript 对象树,它模仿了真实 DOM 树的结构。当应用的状态发生变化时,React 首先会在内存中构建一个新的虚拟 DOM 树,然后将新的虚拟 DOM 树与旧的虚拟 DOM 树进行比较(这个过程叫做“Diffing”或“协调”),找出它们之间的差异。最后,React 只会将这些差异应用到真实的浏览器 DOM 上,而不是重新渲染整个页面。
这种“Diffing”算法和批量更新真实 DOM 的机制,使得 React 在处理大量 UI 更新时非常高效,显著提升了应用的性能。
庞大的社区和生态
React 拥有一个极其活跃和庞大的社区。这意味着当你遇到问题时,很容易在网上找到答案、教程或社区支持。同时,围绕 React 发展起来的生态系统也非常成熟,有大量的第三方库和工具可以帮助你构建各种功能,如路由 (React Router)、状态管理 (Redux, Zustand, Context API)、数据获取 (React Query, SWR) 等等。
2. 准备环境与创建第一个 React 应用
开始编写 React 代码之前,我们需要先搭建开发环境。
安装 Node.js
React 开发需要 Node.js 环境,因为它依赖于 npm(Node 包管理器)或 yarn/pnpm 来安装和管理项目所需的各种依赖库。
访问 Node.js 官网 下载并安装最新版本的 Node.js。安装过程中,npm 会一并安装。建议安装 LTS(长期支持)版本,它更稳定。
安装完成后,打开终端或命令行工具,输入以下命令验证是否安装成功:
bash
node -v
npm -v
如果能看到版本号,说明安装成功。
选择项目构建工具 (Vite)
在过去,create-react-app
是创建 React 项目的标准工具。但现在,Vite 以其闪电般的开发服务器启动速度和极快的模块热更新成为了更受欢迎的选择。Vite 支持多种框架,包括 React。
我们将使用 Vite 来创建我们的第一个 React 项目。
使用 Vite 创建 React 项目
打开你的终端或命令行工具,导航到你想创建项目的目录,然后运行以下命令:
bash
npm create vite@latest my-react-app --template react
这个命令做了几件事:
npm create vite@latest
: 使用 npm 的create
命令来运行最新版本的 Vite 创建工具。my-react-app
: 这是你项目的名称,你可以替换成任何你喜欢的名字。这会在当前目录下创建一个名为my-react-app
的文件夹。--template react
: 告诉 Vite 我们要创建一个基于 React 模板的项目。如果你想使用 TypeScript,可以使用--template react-ts
。
执行命令后,Vite 会创建项目文件。接下来,进入项目目录并安装依赖:
bash
cd my-react-app
npm install
npm install
命令会读取项目根目录下的 package.json
文件,下载所有列出的依赖库到 node_modules
文件夹中。
项目结构概述
使用 Vite 创建的 React 项目结构通常是这样的:
my-react-app/
├── node_modules/ # 所有安装的依赖库
├── public/ # 静态资源文件夹 (如 favicon.ico)
├── src/ # 存放你的源代码
│ ├── assets/ # 存放静态资源 (如图片)
│ ├── App.css # App 组件的样式文件
│ ├── App.jsx # 你的主应用组件
│ ├── index.css # 全局样式文件
│ ├── main.jsx # 项目入口文件
│ └── vite-env.d.ts # Vite 的 TypeScript 声明文件 (如果你选了 ts 模板)
├── .gitignore # Git 忽略文件列表
├── index.html # 应用的入口 HTML 文件
├── package.json # 项目配置和依赖列表
├── vite.config.js # Vite 配置文件
└── README.md # 项目说明文件
index.html
: 这是整个应用的入口 HTML 文件。它非常简单,只有一个<div id="root"></div>
(或其他 ID),你的 React 应用会被“挂载”到这个 DOM 节点上。main.jsx
: 这是 React 应用的入口文件。它负责导入根组件 (App
) 并使用ReactDOM.createRoot
将其渲染到index.html
中的指定 DOM 节点 (#root
)。App.jsx
: 这是你的主应用组件。你将在这个文件中或通过导入其他组件来构建你的 UI。src/
: 这是你编写所有 React 组件和逻辑代码的地方。
运行你的第一个 React 应用
在项目目录下,运行以下命令启动开发服务器:
bash
npm run dev
这个命令会执行 package.json
文件中 scripts
部分定义的 dev
脚本,通常是 vite
。Vite 会启动一个本地开发服务器,通常在 http://localhost:5173
(端口号可能会有变化)。
打开浏览器,访问这个地址,你就能看到 Vite 和 React 的欢迎页面了!
现在,你可以尝试修改 src/App.jsx
中的内容,保存文件后,浏览器中的页面会立即自动更新,这就是 Vite 的热模块更新(HMR)功能,大大提高了开发效率。
恭喜你,你已经成功创建并运行了第一个 React 应用!
3. React 的核心概念 – 组件
组件是 React 应用的基石。理解组件是掌握 React 的关键。
什么是组件?
组件是独立的代码块,它们负责渲染 UI 的特定部分。你可以把它们想象成自定义的 HTML 元素。组件是可复用的、独立的,并且可以接受输入(称为 props)并维护自己的内部状态(称为 state)。
React 中主要有两种类型的组件:
- 函数式组件 (Functional Components): 使用 JavaScript 函数来定义。这是目前 React 推荐和主流的写法,特别是引入 Hook 之后。
- 类组件 (Class Components): 使用 JavaScript 的 class 语法来定义。在 Hook 出现之前,类组件是唯一可以拥有状态和生命周期方法的组件类型。虽然现在推荐使用函数式组件和 Hook,但你仍然可能在旧的项目中看到类组件。
本指南将主要聚焦于函数式组件和 Hook。
创建第一个函数式组件
函数式组件就是一个返回 JSX(我们将在一会儿讨论它)的 JavaScript 函数。
在 src
目录下,创建一个新的文件,比如 src/Greeting.jsx
。
“`javascript
// src/Greeting.jsx
import React from ‘react’; // 在 JSX 文件中通常不需要显式导入 React,因为构建工具会处理,但旧规范或某些配置下可能需要。新版本的 React 和构建工具通常不需要。为了清晰起见,这里保留。
function Greeting() {
return (
你好,React 新手!
这是我的第一个 React 组件。
);
}
// 导出组件,以便其他文件可以导入和使用它
export default Greeting;
“`
这个函数式组件叫做 Greeting
。它返回一个包含 HTML 元素的结构,这个结构用 JSX 语法编写。
渲染组件
要看到这个组件,我们需要在应用的主入口 (main.jsx
) 或另一个组件 (App.jsx
) 中使用它。
修改 src/App.jsx
,移除 Vite 和 React 的默认内容,并引入 Greeting
组件:
“`javascript
// src/App.jsx
import React from ‘react’; // 同样,可以省略
import Greeting from ‘./Greeting’; // 导入 Greeting 组件
import ‘./App.css’; // 如果你不需要 App.css,可以移除
function App() {
return (
这是 App 组件的其他内容。
);
}
export default App;
“`
保存 App.jsx
文件。如果你的开发服务器正在运行 (npm run dev
),浏览器会自动刷新,你将看到 Greeting
组件渲染的内容。
组件的组合
组件最大的优势在于可组合性。你可以在一个组件内部使用另一个组件,构建出任意复杂的 UI 结构。
例如,我们可以在 App
组件中多次使用 Greeting
组件,或者创建一个新的组件来包含 Greeting
。
修改 src/App.jsx
:
“`javascript
// src/App.jsx
import React from ‘react’;
import Greeting from ‘./Greeting’;
import ‘./App.css’;
function App() {
return (
我的应用头部
应用底部信息。
);
}
export default App;
“`
保存后,你会看到页面上渲染了三个“你好,React 新手!”的标题和段落。这就是组件复用的力量。
4. JSX 语法
在上面的例子中,我们看到了类似 HTML 的结构,比如 <div><h1>...</h1></div>
。但它并不是普通的 HTML 字符串,而是 JSX。
JSX 是什么?
JSX 是 JavaScript XML 的缩写。它是一种 JavaScript 的语法扩展,允许你在 JavaScript 代码中书写类似 HTML 的标签结构。
JSX 最终会被 Babel 等工具转换成普通的 JavaScript 函数调用(React.createElement()
),这些函数调用会创建描述 UI 元素的 JavaScript 对象。React 根据这些对象来构建虚拟 DOM。
为什么使用 JSX?
尽管不是强制的(你也可以只使用 React.createElement()
),但 JSX 让 React 代码更具可读性和可写性,它看起来就像我们熟悉的 HTML 结构,使得开发者更容易理解 UI 的层级关系。
JSX 的基本规则
JSX 看似 HTML,但有一些重要的区别和规则需要遵守:
-
必须有一个根元素: 返回的 JSX 结构必须包裹在一个单一的根元素中。
“`jsx
// 正确
return (标题
段落
);
// 错误 (没有单一根元素)
// return (
//标题
//
段落
// );
// 正确 (可以使用 React.Fragment 或 <> 简写,它不会在 DOM 中创建额外的节点)
return (
<>标题
段落
);
“` -
属性使用 camelCase: HTML 属性如
class
在 JSX 中变成className
,for
变成htmlFor
等。这是因为class
和for
是 JavaScript 的保留字。其他属性如id
,src
,alt
等保持不变。
jsx
<div className="container">
<label htmlFor="name">姓名:</label>
<input id="name" type="text" />
</div> -
闭合标签: 所有的标签都必须正确闭合。对于没有子元素的标签(如
<img>
,<input>
,<br>
),必须使用自闭合标签的形式 (/>
)。
jsx
<img src="logo.png" alt="Logo" />
<input type="text" />
<br />
<p>这是一个段落。</p> {/* 普通标签也必须有结束标签 */} -
嵌入 JavaScript 表达式: 你可以在 JSX 中使用大括号
{}
来嵌入任何有效的 JavaScript 表达式(变量、函数调用、计算结果等)。
“`jsx
const name = “React 新手”;
const element =你好, {name}!
; // 嵌入变量
function formatGreeting(user) {
return user ?你好, ${user}!
: ‘你好, 陌生人!’;
}
const user = { name: ‘用户A’ };
const element2 ={formatGreeting(user)}
; // 嵌入函数调用结果
“` -
JSX 是表达式: JSX 本身也是一个表达式,可以赋值给变量、作为函数参数、或者从函数中返回。
在 JSX 中嵌入 JavaScript 表达式
这是 JSX 非常强大的特性。你可以在 {}
中放入任何 JavaScript 表达式。
“`jsx
function App() {
const title = “React 应用”;
const showParagraph = true;
function getYear() {
return new Date().getFullYear();
}
return (
{title}
{/ 嵌入变量 /}
当前年份: {getYear()}
{/ 嵌入函数调用结果 /}
2 + 2 = {2 + 2}
{/ 嵌入计算结果 /}
{/* 嵌入条件逻辑 (后面会详细讲) */}
{showParagraph ? <p>这是一个条件显示的段落。</p> : null}
</div>
);
}
“`
条件渲染 (JSX 中的逻辑判断)
在 React 中,你可以使用 JavaScript 的条件逻辑来决定是否渲染某个元素。
-
if
语句 (在 JSX 外部): 你可以在return
语句之前使用if
来提前返回不同的 JSX。
javascript
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>欢迎回来!</h1>;
}
return <h1>请登录。</h1>;
} -
三元表达式 (
? :
) (在 JSX 内部): 用于简单的条件判断。
jsx
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>欢迎回来!</h1>
) : (
<h1>请登录。</h1>
)}
</div>
);
} -
逻辑与 (
&&
) (在 JSX 内部): 如果条件为真,则渲染后面的元素;如果条件为假,则什么也不渲染。常用于判断某个变量是否存在或某个布尔值为真时渲染一个元素。
jsx
function Greeting({ username }) {
return (
<div>
{/* 如果 username 存在,则显示 "你好, [username]" */}
{username && <h1>你好, {username}!</h1>}
{!username && <p>请填写用户名。</p>}
</div>
);
}
列表渲染 (渲染多个元素)
渲染一个列表(如数组中的元素)是常见的需求。在 React 中,我们通常使用 JavaScript 数组的 map()
方法。
map()
方法会遍历数组中的每个元素,对每个元素执行一个函数,然后返回一个新数组。我们将这个新数组中的每个元素都变成一个 JSX 元素。
“`jsx
function TodoList() {
const todos = [
{ id: 1, text: ‘学习 React’ },
{ id: 2, text: ‘构建一个应用’ },
{ id: 3, text: ‘写一篇博客’ },
];
return (
-
{todos.map(todo => (
- {todo.text}
// 对于列表中的每个元素,返回一个 li 标签
// 注意:这里的 key={todo.id} 非常重要,后面会解释
))}
);
}
“`
Key 属性的重要性:
当你渲染一个列表时,React 要求你为列表中的每个元素指定一个唯一的 key
属性。key
帮助 React 识别列表中哪些元素发生了变化、被添加或被移除。Keys 应该赋予数组中对应的元素,而不是直接赋给组件。选择一个稳定且唯一的 ID 作为 key 通常是最佳实践(如数据库 ID)。避免使用数组索引作为 key,除非你的列表是静态的、不会改变顺序或内容。不使用 key 或者使用不稳定的 key 会导致性能问题和潜在的 bug。
样式处理 (CSS)
在 React 中处理样式有多种方法:
- 普通 CSS: 像传统 HTML 一样,导入
.css
文件。例如,在App.jsx
中导入import './App.css';
。样式会全局生效。 - 内联样式: 使用 JavaScript 对象定义样式,将样式对象作为
style
属性的值。属性名使用 camelCase。
jsx
<p style={{ color: 'blue', fontSize: '16px' }}>这是蓝色文字。</p>
注意:style
属性接受的是一个双层大括号{{}}
,外层表示 JSX 表达式,内层表示一个 JavaScript 对象。 -
CSS Modules: 推荐的方式之一。创建以
.module.css
结尾的文件(如App.module.css
),导入后以对象形式访问类名。这可以避免类名冲突。
“`javascript
// App.module.css
.container {
border: 1px solid #ccc;
padding: 10px;
}// App.jsx
import styles from ‘./App.module.css’;function App() {
return (这段文字使用了 CSS Module 样式。
);
}
``
App_container__abc12`)。
生成的 HTML 会有唯一的类名(如
4. CSS-in-JS 库: 使用 JavaScript 编写 CSS 样式,如 Styled Components, Emotion 等。
对于入门,使用普通 CSS 文件或内联样式就足够了。
5. Props – 组件通信的桥梁
组件是独立的,但它们需要相互通信才能构建复杂的应用。Props (properties 的缩写) 是组件之间传递数据的方式,通常用于父组件向子组件传递数据。
什么是 Props?
Props 是父组件传递给子组件的数据。它们就像 HTML 属性一样,你可以给子组件标签添加自定义属性,这些属性的值就是 Props。
Props 是只读的!子组件不应该修改从父组件接收到的 Props。Props 的流动是单向的:从父到子。
传递 Props
在父组件中,使用子组件时,就像给 HTML 元素添加属性一样传递 Props:
“`jsx
// src/App.jsx
import React from ‘react’;
import Greeting from ‘./Greeting’;
function App() {
const userName = “Alice”;
const userAge = 30;
return (
应用头部
{/ 传递 name 和 age 作为 props /}
);
}
export default App;
“`
这里,App
是父组件,Greeting
是子组件。我们向 Greeting
组件传递了 name
和 age
两个 props。
在组件中接收和使用 Props
在函数式组件中,Props 作为函数的第一个参数被接收,通常是一个对象。你可以直接使用这个对象,或者使用解构赋值来方便地访问单个 prop。
修改 src/Greeting.jsx
:
“`javascript
// src/Greeting.jsx
import React from ‘react’;
// 使用解构赋值获取 name 和 age prop
function Greeting({ name, age }) {
// 如果没有传递 name,可以提供一个默认值
const greetingName = name || ‘陌生人’;
return (
你好,{greetingName}!
{/ 只有当 age prop 存在时才显示年龄信息 /}
{age &&
你的年龄是: {age}
}
);
}
// 导出组件
export default Greeting;
“`
现在,根据你在 App.jsx
中传递的不同 Props,每个 Greeting
组件都会显示不同的内容。
Props 的单向数据流
React 的数据流是自顶向下、单向流动的。父组件通过 Props 将数据传递给子组件。子组件只能使用这些 Props,不能直接修改它们。如果子组件需要修改某个数据,它通常会调用父组件通过 Props 传递下来的一个函数,由父组件来完成数据的修改。这种单向数据流使得数据追踪和应用调试变得更加容易。
默认 Props
你可以为 Props 设置默认值,以防父组件没有传递该 prop。
“`javascript
// src/Greeting.jsx
import React from ‘react’;
function Greeting({ name = ‘陌生人’, age }) { // 在参数中设置默认值
return (
你好,{name}!
{age &&
你的年龄是: {age}
}
);
}
export default Greeting;
“`
现在,即使你在父组件中调用 <Greeting />
而不传递任何 props,name
也会默认显示“陌生人”。
6. State – 管理组件内部数据
State (状态) 是组件内部管理的数据。与 Props 不同,State 是组件自己维护的,并且 State 的变化通常会导致组件的重新渲染。
什么是 State?
State 是组件内部可变的数据。当 State 发生变化时,React 会重新渲染该组件及其子组件,以反映最新的状态。
Props 和 State 的区别:
- Props: 从父组件传递而来,只读,用于组件间通信(自上而下)。
- State: 组件自身维护的内部数据,可变,用于管理组件内部的状态变化。
使用 useState
Hook
在函数式组件中,我们使用 useState
Hook 来管理 State。Hooks 是 React 16.8 引入的特性,它们允许你在不编写 class 的情况下使用 state 和其他 React 特性。
useState
是一个函数,它接收 State 的初始值作为参数,并返回一个包含两个元素的数组:
- 当前 State 的值。
- 一个用于更新 State 的函数。
声明 State 变量
“`javascript
import React, { useState } from ‘react’;
function Counter() {
// 声明一个名为 count 的 state 变量,初始值为 0
// setCount 是一个函数,用于更新 count
const [count, setCount] = useState(0);
return (
你点击了 {count} 次
{/ 事件处理函数,后面会详细讲 /}
);
}
“`
更新 State
通过调用 useState
返回的第二个元素(上面的 setCount
)来更新 State。
javascript
setCount(count + 1); // 将 count 更新为 count + 1
setCount(10); // 将 count 直接设置为 10
重要: 不要直接修改 State 变量 (例如 count = count + 1;
)。必须使用 State 更新函数来更新 State,这样 React 才能检测到变化并重新渲染组件。
State 更新的异步性
State 的更新可能是异步的。这意味着在调用 setCount(count + 1)
后,count
的值并不会立即变成新的值,可能在当前的函数执行完成后,React 才会批量处理 State 更新并重新渲染。
“`javascript
// 这段代码可能有问题,因为 setA 和 setB 依赖于各自上一次的状态,
// 但在同一个事件处理函数中调用,它们可能都基于同一个旧的状态值
// function handleClick() {
// setA(a + 1); // 假设 a = 0, 期望 a = 1
// setB(b + 1); // 假设 b = 0, 期望 b = 1
// // 在这里访问 a 和 b,它们可能仍然是旧值 0
// console.log(a, b);
// }
// 正确的做法是使用函数式更新
// setA(prevA => prevA + 1);
“`
基于上一个 State 更新
如果新的 State 需要基于上一个 State 的值来计算,建议给 State 更新函数传递一个函数,而不是直接传递新值。这个函数会接收上一个 State 的值作为参数,并返回新的 State 值。这是处理基于前一个 State 的更新时最安全的方式,尤其是在异步更新或批量更新的场景下。
“`javascript
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// 使用函数式更新:prevCount 是上一个 count 的值
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1); // 调用两次,count 会加 2
};
return (
你点击了 {count} 次
);
}
“`
一个 State 示例:简单的计数器
让我们把上面的计数器代码整理一下,放在一个组件中:
“`javascript
// src/Counter.jsx
import React, { useState } from ‘react’;
function Counter() {
// 声明一个名为 count 的 state 变量,初始值为 0
// setCount 是一个函数,用于更新 count
const [count, setCount] = useState(0);
// 处理按钮点击事件的函数
const handleIncrement = () => {
// 使用函数式更新,确保是基于最新的 count 值进行递增
setCount(prevCount => prevCount + 1);
};
const handleDecrement = () => {
// 简单递减
setCount(count – 1);
};
return (
计数器示例
当前计数: {count}
{/ 将事件处理函数作为 onClick prop 传递给 button /}
);
}
export default Counter;
“`
然后在 App.jsx
中使用这个 Counter
组件:
“`javascript
// src/App.jsx
import React from ‘react’;
import Greeting from ‘./Greeting’;
import Counter from ‘./Counter’; // 导入 Counter 组件
import ‘./App.css’;
function App() {
return (
我的第一个交互式 React 应用
{/ 分隔线 /}
);
}
export default App;
“`
保存并查看浏览器,你将看到一个功能正常的计数器!
7. 事件处理
在 React 中处理事件(如点击、输入、鼠标移动等)与在原生 JavaScript 中略有不同。
在 React 中处理事件
- 事件命名: React 事件采用 camelCase 命名方式,而不是小写(如
onClick
而不是onclick
)。 - 事件处理函数作为 Props: 你将事件处理函数作为 Props 传递给 DOM 元素或组件。
- 阻止默认行为: 在原生 JavaScript 中,你可能使用
event.preventDefault()
或return false
。在 React 中,你只需在事件处理函数中显式调用event.preventDefault()
。
事件命名和传递
“`jsx
function Button() {
function handleClick() {
alert(‘按钮被点击了!’);
}
return (
// 将 handleClick 函数作为 onClick prop 传递
);
}
“`
访问事件对象
事件处理函数会接收到一个合成事件对象(SyntheticEvent),它是一个跨浏览器的封装器,具有与原生浏览器事件相同的接口。
“`jsx
function Link() {
function handleLinkClick(event) {
event.preventDefault(); // 阻止链接的默认跳转行为
console.log(‘链接被点击,但阻止了默认跳转。’);
}
return (
React 官网
);
}
“`
传递参数给事件处理函数
如果你需要给事件处理函数传递额外参数,可以使用箭头函数或 bind
方法(在函数式组件中箭头函数更常见)。
``jsx
删除 ${item},事件对象: `, event);
function ListItems({ items }) {
function handleDeleteItem(item, event) {
console.log(
// 这里可以执行删除 item 的逻辑
}
return (
-
{items.map(item => (
-
{item}
{/ 使用箭头函数传递参数 /}
))}
);
}
// 在父组件中使用
//
“`
8. 条件渲染与列表渲染深入
我们之前简要介绍了条件渲染和列表渲染,这里再强调一下关键点。
不同的条件渲染方法
if
/else
: 最直观,适合在函数开头根据条件返回不同的 JSX。- 三元表达式 (
condition ? expr1 : expr2
): 适合在 JSX 中根据布尔值在两个元素之间选择一个渲染。 - 逻辑与 (
condition && expr
): 适合在 JSX 中根据布尔值决定是否渲染某个元素。如果condition
为假,&&
后面的部分不会被执行或渲染。 - 元素的直接返回: 如果条件不满足,可以返回
null
或 “ 来表示不渲染任何内容。
选择哪种方法取决于你的具体需求和个人偏好,但应保持一致性,以提高代码可读性。
列表渲染的 Key 属性 (重点!)
再次强调 key
属性的重要性。
“`jsx
-
{items.map(item => (
- {item.text || item}
// key 必须是稳定且唯一的字符串或数字
// 通常使用数据项本身的 ID
// 如果数据项没有 ID,可以使用索引,但要小心副作用 (性能、State 丢失)
))}
“`
- Why Key? 当 React 更新列表时,它会使用
key
来快速匹配新旧列表中的元素。如果没有 key 或 key 不稳定,React 可能会使用默认的行为(如按索引匹配),这在列表顺序变化或插入/删除元素时,可能导致错误地更新 DOM 元素,从而影响性能、出现意想不到的 UI 问题(如输入框状态混乱)或组件状态丢失。 - Key 必须是唯一的: 在同一个列表中,每个元素的 key 都必须是唯一的。
- Key 必须是稳定的: key 不应该在重新渲染时发生变化。
何时可以使用索引作为 Key?
只有在以下两种情况都满足时,才可以使用数组索引作为 key:
- 列表是静态的,不会随时间变化(不增、不删、不排序)。
- 列表中的元素没有唯一的 ID。
即使如此,出于最佳实践考虑,如果能找到稳定的唯一 ID,总是优先使用它。
9. Hooks 简介 (以 useEffect
为例)
Hooks 是 React 16.8 引入的一项重要特性,它们使得在函数式组件中使用 State 和其他 React 特性成为可能,从而避免了类组件的许多复杂性(如 this
的绑定、生命周期方法的理解等)。
什么是 Hook?
Hook 是一些特殊的函数,它们以 use
开头(例如 useState
, useEffect
, useContext
, useReducer
等)。你只能在函数式组件的顶层调用 Hook(不能在循环、条件判断或嵌套函数中调用)。
useEffect
Hook 的作用
useEffect
是一个非常重要的 Hook,它用于处理函数式组件中的副作用 (Side Effects)。副作用是指那些不属于纯计算的操作,例如:
- 数据获取 (Fetching data)
- 订阅或手动修改 DOM
- 设置定时器
在类组件中,这些操作通常在生命周期方法中完成(如 componentDidMount
, componentDidUpdate
, componentWillUnmount
)。useEffect
可以看作是这些生命周期方法的组合,用于在组件渲染后执行某些操作。
useEffect
接收一个函数作为参数,这个函数就是副作用的逻辑。默认情况下,useEffect
会在组件首次渲染后和每次更新后都执行。
“`javascript
import React, { useState, useEffect } from ‘react’;
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// 使用 useEffect 来执行副作用 (数据获取)
useEffect(() => {
console.log(‘组件已挂载或更新,正在执行副作用…’);
// 模拟数据获取
fetch(‘https://api.example.com/items’) // 这是一个示例 URL
.then(response => response.json())
.then(result => {
setData(result);
setLoading(false);
})
.catch(error => {
console.error(‘数据获取失败:’, error);
setLoading(false);
});
// 注意:默认情况下,这个 effect 会在每次渲染后都执行
}); // 没有第二个参数 (依赖项数组)
if (loading) {
return
正在加载数据…
;
}
return (
获取的数据:
{/ 渲染获取到的数据 /}
);
}
“`
清理副作用
有些副作用(如设置订阅、定时器、事件监听)需要在组件卸载时进行清理,以防止内存泄漏。useEffect
的副作用函数可以返回一个清理函数。这个清理函数会在下一次副作用执行之前(如果依赖项变化)或组件卸载时执行。
“`javascript
import React, { useState, useEffect } from ‘react’;
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 设置一个每秒增加 seconds 的定时器
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
console.log('定时器已启动:', intervalId);
// 返回一个清理函数
return () => {
// 在组件卸载或 effect 重新执行前清理定时器
clearInterval(intervalId);
console.log('定时器已清理:', intervalId);
};
}, []); // 注意这里的空数组依赖项
return (
计时: {seconds} 秒
);
}
“`
依赖项数组
useEffect
的第二个参数是一个数组,称为依赖项数组。它告诉 React 这个 effect 依赖于哪些 State 或 Props 的值。只有当依赖项数组中的任何一个值发生变化时,React 才会重新运行副作用函数。
- 没有依赖项数组: effect 会在组件首次渲染后和每次更新后执行。
- 空数组
[]
: effect 只会在组件首次渲染后执行一次(类似于类组件的componentDidMount
)。清理函数(如果存在)会在组件卸载时执行(类似于componentWillUnmount
)。 - 包含依赖项的数组
[dep1, dep2, ...]
: effect 会在首次渲染后执行,并在dep1
或dep2
等任何一个依赖项发生变化时重新执行。清理函数会在下一次 effect 执行前或组件卸载时执行。
重要: 确保依赖项数组包含了副作用函数内部使用的所有外部变量(Props、State、函数),否则可能会导致 bugs(比如使用了旧的 State 值)。ESLint 的 eslint-plugin-react-hooks
插件可以帮助你检查是否遗漏了依赖项。
10. 接下来做什么?
恭喜你!你已经掌握了 React 的基本概念:组件、JSX、Props、State、事件处理和 useEffect
Hook。这为你构建现代前端应用奠定了坚实的基础。
React 世界还有很多内容可以探索:
- 路由 (Routing): 大多数单页应用需要根据 URL 显示不同的页面。学习 React Router 是下一步很自然的步骤。
- 状态管理 (State Management): 随着应用越来越大,多个组件需要共享和修改同一个状态,这时简单的 Props 和 State 可能就不够了。学习 Context API(React 内置)或 Redux、Zustand、Jotai 等第三方库是必要的。
- 数据获取 (Data Fetching): 在实际应用中,你需要从后端 API 获取数据。学习如何使用
fetch
API、Axios 或更高级的库如 React Query、SWR 来处理数据获取、缓存、同步和错误处理。 - 深入学习 Hooks: 探索其他常用的 Hook,如
useContext
、useReducer
、useRef
、useMemo
、useCallback
以及自定义 Hook。 - 表单处理: 学习如何在 React 中构建和管理表单,包括受控组件和非受控组件。
- 性能优化: 了解
React.memo
、useMemo
、useCallback
等 Hook 如何帮助优化组件性能。 - 测试: 学习如何为你的 React 组件编写单元测试和集成测试(如使用 Jest 和 React Testing Library)。
- TypeScript: 将 TypeScript 应用于 React 项目,可以提高代码的健壮性和可维护性。
- 下一代框架 (Meta-frameworks): 学习 Next.js 或 Remix 等基于 React 的全栈框架,它们提供了服务器端渲染 (SSR)、静态网站生成 (SSG)、API 路由等强大功能,适合构建更复杂的应用。
学习建议:
- 实践是王道: 理论知识固然重要,但动手实践是巩固知识和发现问题的最好方法。尝试构建一些小项目,比如待办事项列表、天气应用、简单的博客等。
- 阅读官方文档: React 官方文档 是最权威、最全面的学习资源。当遇到不明白的概念或想深入了解某个特性时,查阅文档是最佳选择。
- 参与社区: 加入 React 相关的论坛、社群、GitHub 仓库,与其他开发者交流,学习他们的经验。
- 阅读源码: 如果有兴趣,可以尝试阅读一些优秀的 React 开源项目的源码。
11. 总结
React 通过引入声明式编程、组件化和虚拟 DOM,极大地简化了构建复杂用户界面的过程。
在本指南中,我们学习了:
- React 的优势和核心理念。
- 如何使用 Vite 快速搭建一个 React 开发环境。
- 组件(特别是函数式组件)的概念、创建和使用。
- JSX 语法及其规则,以及如何在其中嵌入 JavaScript 表达式。
- 如何使用 Props 在组件间传递数据。
- 如何使用
useState
Hook 管理组件内部状态。 - 如何在 React 中处理用户事件。
- 条件渲染和列表渲染的方法,以及
key
属性的重要性。 useEffect
Hook 的基本用法,用于处理副作用和清理。
这仅仅是 React 世界的起点。React 强大且灵活,能够适应各种规模的应用需求。持续学习和实践是掌握它的不二法门。
希望本指南为你开启 React 之旅提供了一个良好的开端。祝你在前端开发的道路上越走越远,构建出令人惊叹的应用!
祝你学习愉快!