React 基础教程:带你进入现代前端世界
引言:前端开发的演变与 React 的崛起
在过去几年里,前端开发领域经历了巨大的变革。曾经,我们主要依赖 HTML 构建骨架,CSS 美化样式,JavaScript 操控 DOM(文档对象模型)来实现页面交互。这种传统的开发模式在处理小型、静态的网站时尚可应对,但随着 Web 应用变得越来越复杂、动态化需求不断增加,直接操作 DOM 的方式开始暴露出维护困难、性能瓶颈等问题。
开发者们开始寻求更高效、更具组织性的方式来构建复杂的用户界面。各种前端框架和库应运而生,其中,由 Facebook(现 Meta)开发的 React 凭借其独特的设计思想和卓越的性能表现,迅速赢得了全球开发者的青睐,成为了构建现代 Web 应用和移动应用(React Native)的首选技术之一。
React 的核心理念在于“组件化”和“声明式编程”。它将复杂的 UI 分解成一个个独立、可复用的组件,并通过声明式的方式描述 UI 的状态,React 会负责高效地更新 UI 以匹配状态的变化,极大地简化了前端应用的开发和维护。
如果你渴望掌握现代前端开发的核心技术,那么学习 React 是一个非常明智的选择。本篇文章将作为你的入门向导,带你从零开始,逐步理解 React 的核心概念,并亲手搭建一个简单的应用,推开现代前端世界的大门。
第一章:为什么选择 React?它解决了哪些痛点?
在我们深入学习 React 的具体用法之前,理解它为什么如此受欢迎至关重要。React 主要解决了传统前端开发中的几个关键痛点:
-
命令式 vs. 声明式:
- 命令式 (Imperative): 传统的 JavaScript 操作 DOM 是命令式的。你需要一步步告诉浏览器“找到这个元素,改变它的文本内容,再给它添加一个类”。当状态变化时,你需要手动执行一系列 DOM 操作来更新 UI。这使得代码变得冗长、难以追踪和维护。
- 声明式 (Declarative): React 采用声明式的方式。你只需告诉 React“当数据是这样时,UI 应该是这样的”。当数据变化时,你只需要更新数据(状态),React 会自动、高效地计算出需要对 DOM 进行哪些更改,然后执行这些更改。开发者不再需要关心 DOM 操作的细节,只需描述最终的 UI 状态。这使得代码更加简洁、易于理解和预测。
-
DOM 操作效率问题: 直接、频繁地操作原生 DOM 往往是低效的,因为它涉及到浏览器布局、渲染等复杂过程。React 引入了 虚拟 DOM (Virtual DOM)。虚拟 DOM 是一个内存中的 UI 表示,它与真实的 DOM 树一一对应。当状态变化时,React 会首先更新虚拟 DOM,然后通过 Diffing 算法比较新旧虚拟 DOM 树的差异,找出需要更新的最小部分,最后只对真实 DOM 进行必要的、批量化的修改。这大大减少了真实 DOM 操作,提高了性能。
-
代码组织与复用: 大型应用的代码量庞大,如何有效地组织代码是关键。React 推崇组件化 (Component-Based) 开发。将 UI 拆分成独立的、可复用的组件(如按钮、列表项、导航栏等)。每个组件有自己的逻辑和外观,可以像乐高积木一样组合起来构建复杂的界面。这提高了代码的模块化程度、可维护性和复用性。
-
跨平台能力: React 不仅用于 Web 开发,React Native 允许你使用相同的 React 理念和 JavaScript 语言开发原生 iOS 和 Android 应用,极大地提高了开发效率和代码复用率。
总而言之,React 提供了一种高效、灵活且易于维护的构建用户界面的方式。通过声明式、组件化和虚拟 DOM,它简化了复杂应用的开发过程,提高了性能,并促进了代码的复用。
第二章:搭建你的第一个 React 开发环境
开始 React 开发的第一步是搭建合适的开发环境。最简单快捷的方式是使用官方或社区提供的项目创建工具。
2.1 Node.js 和 npm/yarn/pnpm
React 项目的构建、依赖管理和运行都依赖于 Node.js。请确保你的计算机上安装了 Node.js。安装 Node.js 时通常会一并安装 npm (Node Package Manager)。你也可以选择使用 yarn 或 pnpm 作为包管理器,它们通常安装和管理依赖更快。
你可以在 Node.js 官网(https://nodejs.org/)下载适合你操作系统的安装包。安装完成后,打开终端或命令行工具,输入以下命令检查是否安装成功:
“`bash
node -v
npm -v
如果你安装了 yarn
yarn -v
如果你安装了 pnpm
pnpm -v
“`
2.2 创建你的第一个 React 应用
过去,create-react-app
(CRA) 是官方推荐的快速创建 React 项目的工具。虽然它仍然可用,但由于其构建速度较慢且功能相对固定,现在社区更倾向于使用像 Vite 这样更轻量、更快速的构建工具。
我们将以 Vite 为例来创建一个 React 项目。
首先,打开你的终端,导航到你想要创建项目的目录,然后运行以下命令:
“`bash
使用 npm
npm create vite@latest my-react-app –template react
或者使用 yarn
yarn create vite my-react-app –template react
或者使用 pnpm
pnpm create vite my-react-app –template react
“`
解释一下这个命令:
* npm create vite@latest
: 使用 npm 的 create
命令来运行 vite
包的最新版本。
* my-react-app
: 这是你的项目名称,你可以改成任何你喜欢的名字。
* --template react
: 指定使用 react
模板来初始化项目。Vite 支持多种框架模板,这里我们选择 React。
运行命令后,Vite 会快速生成项目文件。然后,你需要进入项目目录并安装依赖:
“`bash
cd my-react-app
使用 npm
npm install
或者使用 yarn
yarn install
或者使用 pnpm
pnpm install
“`
安装完成后,你就可以启动开发服务器了:
“`bash
使用 npm
npm run dev
或者使用 yarn
yarn dev
或者使用 pnpm
pnpm dev
“`
通常,开发服务器会启动在 http://localhost:5173
(或其他端口)。打开浏览器访问这个地址,你应该能看到一个 Vite 和 React 的欢迎页面。恭喜你,你的第一个 React 应用已经成功运行起来了!
2.3 项目结构概览
Vite 创建的 React 项目结构通常比较简洁:
my-react-app/
├── node_modules/ # 项目依赖的第三方库
├── public/ # 静态文件(如 index.html, favicon.ico),不会被处理
├── src/ # 你的源代码目录
│ ├── assets/ # 存放静态资源(图片、字体等)
│ ├── App.css # 应用的 CSS 样式
│ ├── App.jsx # 根组件文件
│ ├── index.css # 全局 CSS 样式
│ ├── main.jsx # 应用的入口文件
│ └── react.svg # React logo 图片
├── .gitignore # Git 忽略文件配置
├── index.html # 应用的 HTML 入口文件
├── package.json # 项目的元数据、依赖和脚本配置
├── vite.config.js # Vite 配置文
└── README.md # 项目说明
index.html
: 这是应用的唯一 HTML 文件。注意其中只有一个空的div
标签,通常 id 为root
或app
。React 将会把整个应用渲染到这个标签内部。src/main.jsx
: 这是应用的入口文件。它负责创建 React 根并将主组件 (App
) 渲染到index.html
中指定的 DOM 元素里。src/App.jsx
: 这是应用的根组件。你可以在这里构建你的整个应用 UI。src/
目录下的其他文件通常是组件、样式、工具函数等。
现在,我们已经搭建好了环境,接下来我们将深入学习 React 的核心概念。
第三章:JSX – React 的语法糖
React 使用一种叫做 JSX (JavaScript XML) 的语法来描述 UI。它看起来像 HTML,但实际上是在 JavaScript 代码中编写的。
jsx
// 这是一个 JSX 表达式
const element = <h1>Hello, React!</h1>;
为什么使用 JSX?
- 直观: 它让你可以用熟悉的类似 HTML 的结构来描述 UI,非常直观。
- 组件化: 可以直接在 JSX 中使用自定义组件。
- 表达力: JSX 允许你将 JavaScript 逻辑(如变量、表达式)直接嵌入到标记中。
JSX 不是标准的 JavaScript 语法,浏览器无法直接识别。在项目构建过程中(Vite 或 Babel),JSX 会被转换 (transpile) 成普通的 JavaScript 代码,例如上面那行 JSX 会被转换成 React.createElement('h1', null, 'Hello, React!')
这样的函数调用。
3.1 JSX 基本规则
-
单一根元素: 每个 JSX 表达式必须有一个单一的根元素。
“`jsx
// 正确
const element = (Title
Content
);
// 错误
/
const element = (Title
Content
);
/// 或者使用 Fragment (<>)
const element = (
<>Title
Content
);
``
<>…` 是一个 Fragment 的简写形式,它不会在 DOM 中产生额外的节点。 -
在 JSX 中嵌入 JavaScript 表达式: 使用花括号
{}
在 JSX 中嵌入 JavaScript 变量、表达式或函数调用。“`jsx
const name = ‘React User’;
const element =Hello, {name}!
; // 嵌入变量
const sum = 10 + 20;
const result =The sum is: {sum}
; // 嵌入表达式
function formatName(user) {
return user.firstName + ‘ ‘ + user.lastName;
}
const user = { firstName: ‘Jane’, lastName: ‘Doe’ };
const greeting =Hello, {formatName(user)}!
; // 嵌入函数调用
“` -
属性 (Attributes): 属性的书写方式与 HTML 类似,但有几点不同:
- class -> className: HTML 中的
class
在 JSX 中要写成className
,因为class
是 JavaScript 的保留关键字。 - for -> htmlFor: HTML 中的
for
属性(用于<label>
关联表单元素)在 JSX 中要写成htmlFor
。 - 驼峰命名法: 大多数 HTML 属性在 JSX 中使用驼峰命名法(如
onClick
,onChange
,readOnly
),但少数常用属性如href
,src
,alt
等仍然是小写。 - 布尔属性: 如果属性值为
true
,可以只写属性名(如<input disabled />
)。如果值为false
,则属性不会被添加。 - 行内样式: 行内样式使用一个 JavaScript 对象来表示,属性名采用驼峰命名法。
“`jsx
const imageUrl = ‘logo.svg’;
const styles = {
color: ‘blue’,
fontSize: ’16px’ // 注意这里的驼峰命名
};const element = (
{/ 布尔属性 /});
“` - class -> className: HTML 中的
-
自闭合标签: 没有子元素的标签必须使用自闭合形式
/>
。jsx
const element = <img src="path/to/image.png" />; -
注释: 在 JSX 中,注释需要放在花括号
{}
中,并使用 JavaScript 的多行注释语法/* ... */
或单行注释//
。jsx
const element = (
<div>
{/* 这是一个 JSX 注释 */}
{/*
这是
多行
注释
*/}
<h1>Hello</h1> {/* 单行注释也可以 */}
</div>
);
掌握 JSX 是学习 React 的第一步,它为你构建 UI 提供了一种直观且富有表现力的方式。
第四章:组件 – 构建 UI 的基石
组件是 React 应用的核心。一个组件就是一个独立、可复用的 UI 单元。在 React 中,我们主要使用函数组件 (Functional Components) 来构建应用。
4.1 函数组件的创建与使用
函数组件就是一个普通的 JavaScript 函数,它接收一个参数(通常叫做 props
),并返回 React 元素(通常是通过 JSX 创建的)。
“`jsx
// 定义一个简单的函数组件
function WelcomeMessage() {
return
Hello, Welcome to my App!
;
}
// 在另一个组件中使用它
function App() {
return (
This is the main application.
);
}
// 将 App 组件渲染到 DOM 中 (通常在 src/main.jsx)
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import App from ‘./App.jsx’;
import ‘./index.css’;
ReactDOM.createRoot(document.getElementById(‘root’)).render(
);
“`
在上面的例子中:
* WelcomeMessage
是一个函数组件,它返回一个 <h1>
元素。
* App
也是一个函数组件,它在其返回的 JSX 中使用了 <WelcomeMessage />
。注意自定义组件名总是以大写字母开头,这是 React 用来区分原生 HTML 元素和自定义组件的方式。
* main.jsx
文件使用 ReactDOM.createRoot().render()
方法将根组件 App
渲染到 HTML 页面中 id 为 root
的 DOM 元素里。<React.StrictMode>
是一个开发工具,用于检测潜在问题。
4.2 组件的嵌套与组合
组件的最大优势在于其可组合性。你可以将小的组件组合成大的组件,以此类推,构建出整个应用界面。
“`jsx
function Header() {
return (
My Awesome App
);
}
function MainContent() {
return (
Here is some content.
);
}
function Footer() {
return (
);
}
function App() {
return (
);
}
“`
通过这种方式,我们将整个页面结构分解成了 Header、MainContent 和 Footer 三个独立的组件,然后在 App 组件中将它们组合起来。每个组件都可以独立开发、测试和维护。
第五章:Props – 组件之间的数据传递
组件通常需要从外部接收数据来渲染不同的内容。在 React 中,我们通过 Props (Properties) 来实现父组件向子组件传递数据。Props 是传递给组件的只读属性集合。
5.1 传递 Props
在父组件中使用子组件时,你可以像给 HTML 标签添加属性一样给子组件添加 props:
“`jsx
// 定义一个子组件,它将接收一个名为 ‘name’ 的 prop
function Greeting(props) {
// props 参数是一个对象,包含了父组件传递的所有属性
return
Hello, {props.name}!
;
}
// 父组件向子组件传递 prop
function App() {
const userName = ‘Alice’;
const userAge = 30;
return (
{/* 传递一个变量 prop (使用花括号) */}
<Greeting name={userName} />
{/* 传递多个 props */}
<UserProfile name="Charlie" age={userAge} />
</div>
);
}
// 另一个接收多个 props 的子组件
function UserProfile(props) {
return (
User: {props.name}, Age: {props.age}
);
}
“`
5.2 在子组件中接收和使用 Props
子组件通过函数的第一个参数来接收 props。这个参数是一个对象,其键是父组件传递的属性名,值是对应的数据。
“`jsx
// 使用 props 对象
function Greeting(props) {
return
Hello, {props.name}!
;
}
// 使用 ES6 对象解构简化 props 的使用
function GreetingUsingDestructuring({ name }) {
return
Hello, {name}!
;
}
// 使用多个 props 并解构
function UserProfile({ name, age }) {
return (
User: {name}, Age: {age}
);
}
“`
使用解构赋值可以使代码更简洁,特别是当组件需要接收多个 props 时。
5.3 Props 的只读性
Props 是只读的 (Read-Only)。这意味着子组件不能修改它从父组件接收到的 props。如果子组件需要根据接收到的数据来管理自己的内部状态,或者需要向上通知父组件数据发生了变化,则需要使用 State(下一章介绍)或其他机制。
这个规则是 React 单向数据流的核心部分,它使得数据流向更易于理解和调试。
第六章:State – 组件的内部状态管理
除了从父组件接收数据(Props),组件有时还需要管理自己的内部数据,这些数据可能会随着用户交互或其他事件而发生变化,并影响组件的渲染。这种内部、可变的数据叫做 State (状态)。
在函数组件中,我们使用 Hooks 来管理 State。最基本的 State Hook 是 useState
。
6.1 使用 useState Hook
首先,你需要从 ‘react’ 包中导入 useState
:
jsx
import React, { useState } from 'react';
useState
是一个函数,它接收一个参数作为 State 的初始值。它返回一个包含两个元素的数组:
1. 当前 State 的值。
2. 一个用于更新 State 的函数(通常称为 setter 函数)。
“`jsx
function Counter() {
// 声明一个 State 变量 ‘count’,初始值为 0
// useState 返回一个数组:[当前值, 更新函数]
const [count, setCount] = useState(0);
// count 的当前值是 0
// setCount 是一个函数,调用它可以更新 count 的值
const increment = () => {
// 调用 setCount 来更新 count 的值
// 这样会触发组件的重新渲染
setCount(count + 1);
};
return (
Count: {count}
);
}
“`
6.2 更新 State
更新 State 必须使用 useState
返回的 setter 函数(上面的 setCount
)。切勿直接修改 State 变量的值(例如 count = count + 1
),这样做不会触发组件的重新渲染,导致 UI 不更新。
当你调用 setter 函数时,React 会:
1. 用新的值更新 State。
2. 重新渲染组件。
State 更新可能是异步的: React 可能会批量处理多个 State 更新,以提高性能。因此,如果你需要基于旧的 State 值来更新 State,最好使用函数形式的 setter:
“`jsx
const increment = () => {
// 使用函数形式更新 State,保证基于最新的 State 值进行计算
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => prevCount – 1);
};
const reset = () => {
setCount(0); // 直接设置初始值
};
``
setCount(prevCount => prevCount + 1)
函数形式的接收前一个 State 值
prevCount` 作为参数,并返回新的 State 值。这种方式在连续多次更新 State 或更新依赖于前一个 State 的场景下非常有用。
6.3 多个 State 变量
一个组件可以使用 useState
Hook 声明多个 State 变量:
“`jsx
function UserForm() {
const [name, setName] = useState(”); // 字符串类型 State
const [age, setAge] = useState(0); // 数字类型 State
const [isSubscribed, setIsSubscribed] = useState(false); // 布尔类型 State
const handleNameChange = (event) => {
setName(event.target.value);
};
const handleAgeChange = (event) => {
setAge(Number(event.target.value)); // 将输入值转换为数字
};
const handleSubscriptionChange = (event) => {
setIsSubscribed(event.target.checked);
};
return (
Summary: {name} ({age}), Subscribed: {isSubscribed ? ‘Yes’ : ‘No’}
);
}
``
useState` 调用来管理表单的三个不同输入字段的状态。
在这个例子中,我们使用三个独立的
State 是组件的“记忆”,它使得组件能够响应用户交互并更新自己的内容。理解并熟练使用 State 是构建交互式 React 应用的关键。
第七章:事件处理
在 React 中处理事件(如点击按钮、输入文本等)与原生 HTML 类似,但有一些 JSX 特有的语法。
7.1 添加事件监听器
事件监听器作为元素的属性添加到 JSX 元素上,属性名通常是驼峰命名法(如 onClick
, onChange
, onSubmit
),属性值是一个 JavaScript 函数。
“`jsx
function MyButton() {
function handleClick() {
alert(‘Button clicked!’);
}
return (
);
}
function MyInput() {
function handleChange(event) {
console.log(‘Input value:’, event.target.value);
}
return (
);
}
“`
7.2 事件对象
与原生 JavaScript 事件处理函数类似,React 的事件处理函数也会接收一个合成事件对象 (SyntheticEvent) 作为参数。这个合成事件对象是对原生浏览器事件的封装,它具有与原生事件相似的接口,但在不同浏览器之间保持一致性。
你可以通过事件对象访问事件的相关信息,例如:
* event.target
: 触发事件的 DOM 元素。
* event.preventDefault()
: 阻止默认行为(如阻止表单提交、阻止链接跳转)。
* event.stopPropagation()
: 阻止事件冒泡。
“`jsx
function MyForm() {
function handleSubmit(event) {
event.preventDefault(); // 阻止表单默认提交行为
alert(‘Form submitted!’);
}
return (
);
}
“`
7.3 传递参数给事件处理函数
如果你需要在调用事件处理函数时传递额外的参数,可以使用箭头函数或 bind
方法:
“`jsx
function ItemList({ items }) {
function handleDelete(item) {
console.log(‘Deleting item:’, item);
// 实际应用中这里会更新 State 或调用父组件传递的删除函数
}
return (
-
{items.map(item => (
-
{item.text}
{/ 使用箭头函数传递参数 /}
{/* 或者使用 bind 方法 */} {/* <button onClick={handleDelete.bind(this, item)}>Delete</button> */} </li> ))} </ul>
);
}
``
() => handleDelete(item)
使用箭头函数是比较常见的做法,它创建了一个新的函数,当事件发生时,这个新函数会被调用,并在其内部调用
handleDelete函数并传递
item` 作为参数。事件处理是构建交互式应用不可或缺的一部分。React 的事件系统提供了跨浏览器一致的事件处理方式。
第八章:条件渲染与列表渲染
构建动态 UI 意味着你需要根据不同的条件渲染不同的内容,或者渲染一组相似的元素(如列表)。React 提供了直观的方式来实现这些功能。
8.1 条件渲染 (Conditional Rendering)
你可以使用标准的 JavaScript 控制流语句(如
if/else
)或逻辑运算符在 JSX 中实现条件渲染。-
使用
if
语句 (在 JSX 外部):jsx
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
} else {
return <h1>Please log in.</h1>;
}
}
这是最直接的方式,根据条件返回不同的 JSX 元素。 -
使用三元运算符 (
condition ? true : false
) (在 JSX 内部):jsx
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please log in.</h1>
)}
</div>
);
}
适用于需要在一行内根据条件渲染不同内容的场景。 -
使用逻辑
&&
运算符 (条件为真时渲染):jsx
function Mailbox({ unreadMessages }) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && (
<h2>
You have {unreadMessages.length} unread messages.
</h2>
)}
</div>
);
}
如果左侧的条件为true
,则渲染右侧的 JSX;如果条件为false
,则整个表达式返回false
,React 不会渲染任何内容。适用于条件为真时才需要渲染某个元素的场景。 -
使用逻辑
||
运算符 (提供备用内容):jsx
function UserProfile({ username }) {
// 如果 username 为空或 null,则显示 'Guest'
const displayName = username || 'Guest';
return <p>Hello, {displayName}!</p>;
}
如果左侧表达式为“假值”(如 null, undefined, ”, 0, false),则返回右侧表达式的值。
8.2 列表渲染 (List Rendering)
渲染一组数据项通常使用 JavaScript 的
map()
方法来遍历数组,并为每个项生成一个 JSX 元素。“`jsx
function TodoList() {
const todos = [
{ id: 1, text: ‘Learn React’ },
{ id: 2, text: ‘Build a project’ },
{ id: 3, text: ‘Deploy app’ }
];return (
-
{todos.map(todo => (
- 元素
- {/ !!! 必须添加 key 属性 !!! /}
{todo.text}
// 为列表中的每个项渲染一个))}
);
}
“`关于
key
属性:
在渲染列表时,务必为列表中的每个元素添加一个唯一的key
属性。key
帮助 React 识别哪些列表项发生了变化、被添加或被移除。它有助于 React 更高效地更新 UI,尤其是在列表项的顺序可能改变或列表项被增删的情况下。key
的值应该是稳定的、唯一的: 通常使用数据项的 ID 作为key
。如果数据没有唯一的 ID,可以使用数组项的索引作为备用key={index}
,但只在列表项不会改变、增删或重新排序的简单场景下使用索引作为 key。否则,使用索引可能导致性能问题和 UI 渲染错误。key
只需要在map()
方法中的最外层元素上添加。
条件渲染和列表渲染是构建动态和数据驱动 UI 的基础。掌握它们可以让你根据不同的数据状态呈现多样化的界面。
第九章:Hooks 简介 (useEffect)
前面我们已经接触了
useState
Hook 用于管理组件的状态。React Hooks 是一系列函数,它们允许你在函数组件中使用 State 和其他 React 特性(如生命周期方法、Context 等),而无需编写 Class 组件。Hooks 的出现是 React 函数组件的一次重大升级,使得函数组件能够承担更复杂的功能,同时也让代码更加简洁、易于测试和复用。
除了
useState
,另一个非常常用的 Hook 是useEffect
。9.1 useEffect Hook 的作用
useEffect
用于在函数组件中处理副作用 (Side Effects)。副作用是指那些不直接参与渲染、但在组件渲染后需要执行的操作,例如:
* 数据获取 (Fetching data from an API)
* 订阅外部数据源 (Setting up subscriptions)
* 手动修改 DOM (虽然通常应避免,但在少数情况下可能需要)
* 设置计时器或清除计时器可以理解为,
useEffect
让你在组件渲染到屏幕上后,以及在后续更新或卸载时执行一些操作。9.2 useEffect 的基本用法
useEffect
接收一个函数作为参数,这个函数包含了你想执行的副作用代码。“`jsx
import React, { useState, useEffect } from ‘react’;function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);// 使用 useEffect 来执行副作用
useEffect(() => {
// 副作用函数体
console.log(‘Fetching data for user:’, userId);// 模拟数据获取 const fetchData = async () => { setLoading(true); try { // 真实场景:const response = await fetch(`/api/users/${userId}`); // 真实场景:const result = await response.json(); // 模拟异步操作 const result = await new Promise(resolve => setTimeout(() => resolve({ id: userId, name: `User ${userId}`, email: `user${userId}@example.com` }), 1000) ); setData(result); setError(null); } catch (err) { setError(err); setData(null); } finally { setLoading(false); } }; fetchData(); // 返回一个清理函数(可选) return () => { // 组件卸载或副作用重新执行前执行这里的代码 console.log('Cleaning up effect...'); // 真实场景:取消网络请求、清除定时器、取消订阅等 };
}, [userId]); // !!! 依赖项数组 !!!
if (loading) return
Loading…
;
if (error) returnError: {error.message}
;
if (!data) return null;return (
User Details
ID: {data.id}
Name: {data.name}
Email: {data.email}
);
}
“`解释
useEffect
的第二个参数:依赖项数组 (Dependency Array)。
*[userId]
: 这是依赖项数组。useEffect
会在组件首次渲染后执行一次副作用函数。然后,只有当数组中的任何一个依赖项发生变化时,useEffect
的副作用函数才会重新执行。在上面的例子中,当userId
这个 prop 发生变化时,effect 会重新运行,从而根据新的用户 ID 获取数据。
*[]
(空数组): 如果依赖项数组是空的,effect 只会在组件首次渲染后执行一次,类似于类组件的componentDidMount
。它不会在后续的组件更新时重新执行。适用于只执行一次的副作用(如初始化)。
* 没有第二个参数: 如果省略依赖项数组,effect 会在组件首次渲染后以及每一次组件更新后都执行。通常应该避免这种情况,除非你的副作用确实需要在每次渲染后都运行。清理函数:
useEffect
的副作用函数可以返回一个函数。这个返回的函数就是清理函数。它会在以下时机执行:
1. 在组件卸载前。
2. 在 effect 重新执行前(如果依赖项发生变化)。清理函数通常用于清除副作用造成的“残留”,比如取消网络请求、清除定时器、移除事件监听器等,以防止内存泄漏。
useEffect
是一个功能强大的 Hook,理解其依赖项数组和清理函数对于正确管理组件的副作用至关重要。第十章:构建一个简单的计数器应用 (实践出真知)
理论知识学了不少,现在让我们把前面学到的概念结合起来,构建一个简单的计数器应用。
需求:
一个页面显示当前的数字,有两个按钮:“增加”和“减少”。点击“增加”数字加一,点击“减少”数字减一。“`jsx
import React, { useState } from ‘react’; // 导入 useState Hookfunction Counter() {
// 1. 使用 useState 声明一个 State 变量 ‘count’,初始值为 0
const [count, setCount] = useState(0);// 2. 定义事件处理函数来更新 State
const handleIncrement = () => {
// 调用 setCount,使用函数形式确保基于最新的 count 值更新
setCount(prevCount => prevCount + 1);
};const handleDecrement = () => {
// 调用 setCount,使用函数形式确保基于最新的 count 值更新
setCount(prevCount => prevCount – 1);
};// 3. 返回 JSX 来描述 UI
return ({/ 4. 显示当前的 State 值 /}Counter
Count: {count}
{/* 5. 为按钮添加事件监听器 */} <button onClick={handleIncrement}>Increase</button> <button onClick={handleDecrement}>Decrease</button> </div>
);
}// 将 Counter 组件渲染到页面中 (在 main.jsx 中调用
)
// import ReactDOM from ‘react-dom/client’;
// import Counter from ‘./Counter’; // 假设 Counter 组件在 Counter.jsx 文件中
// const root = ReactDOM.createRoot(document.getElementById(‘root’));
// root.render(); export default Counter; // 导出组件以便在其他地方使用
“`操作步骤:
- 在你项目
src
目录下创建一个新文件,例如Counter.jsx
。 - 将上面的代码粘贴到
Counter.jsx
文件中。 -
打开
src/App.jsx
文件,清空其内容(或者保留顶部的 import),然后导入并使用你的Counter
组件:“`jsx
import React from ‘react’;
import Counter from ‘./Counter’; // 导入 Counter 组件
import ‘./App.css’; // 如果你有 App.cssfunction App() {
return ({/ 在 App 组件中使用 Counter 组件 /}
);
}export default App;
“`
4. 保存文件,你的开发服务器应该会自动重新加载页面。现在你应该能看到一个简单的计数器界面,点击按钮,数字会随之变化。
通过这个简单的例子,你已经实践了:
* 创建函数组件
* 使用useState
Hook 管理 State
* 使用事件处理函数 (onClick
)
* 在 JSX 中显示 State 的值这是一个非常基础但重要的实践,它串联起了前面学习的几个核心概念。
第十一章:超越基础 – 进一步学习的方向
本教程带你了解了 React 的核心基础知识,但现代前端世界远不止这些。掌握了基础后,你可以继续深入学习以下重要概念和技术:
- 更多 Hooks: 学习其他常用的 Hooks,如
useContext
(用于跨组件层级共享数据)、useReducer
(用于管理更复杂的 State 逻辑)、useRef
(用于访问 DOM 节点或在多次渲染之间保持可变值)、useMemo
和useCallback
(用于性能优化)。 - 组件通信: 除了 Props (父传子),还需要学习如何实现子组件向父组件传递数据(通常通过回调函数 Props)以及非父子组件之间的通信(如 Context 或全局状态管理库)。
- Context API: React 内置的 Context API 提供了一种在组件树中无需通过每层手动传递 Props 就能共享数据的方式,适用于主题切换、用户认证信息等场景。
- 路由 (Routing): 对于单页面应用 (SPA),你需要学习如何使用路由库(如 React Router)来管理不同的页面和 URL。
- 状态管理 (State Management): 对于大型复杂应用,使用 Redux、Zustand、MobX 等状态管理库可以更好地组织和管理全局应用状态。
- 数据获取 (Data Fetching): 学习如何在 React 组件中优雅地获取数据,常用的库有 Axios、Fetch API 结合 Hook (如 SWR, React Query) 等。
- 样式化 (Styling): 探索不同的 React 样式化方案,如 CSS Modules, Styled Components, Emotion 等。
- TypeScript: 将类型系统引入你的 React 项目,提高代码的可维护性和健壮性。
- 测试 (Testing): 学习如何编写单元测试、集成测试和端到端测试来保证应用的质量,常用的工具有 Jest, React Testing Library, Cypress 等。
- 性能优化: 学习如何使用 React 提供的工具(如 React Profiler)和技术(如代码分割、懒加载、Memoization)来提升应用性能。
- 服务端渲染 (SSR) / 静态站点生成 (SSG): 对于需要更好的 SEO 或首屏加载速度的应用,可以学习 Next.js 或 Remix 等支持 SSR/SSG 的 React 框架。
学习是一个持续的过程。从基础开始,逐步深入,不断实践,你将能够构建越来越复杂和强大的现代前端应用。
结论:开启你的现代前端之旅
恭喜你走到了这里!通过本篇文章的学习,你已经了解了 React 的核心理念、环境搭建、JSX 语法、组件、Props、State、事件处理、条件渲染、列表渲染以及 Hooks 的基本用法。这些是构建任何 React 应用的基石。
React 是一门强大的工具,但它也只是现代前端生态系统的一部分。不断探索新的工具、库和最佳实践,保持学习的热情,你就能在这个充满活力的领域中不断进步。
记住,最好的学习方式是实践。尝试修改本文中的代码示例,构建你自己的小项目,比如一个简单的待办事项列表、一个天气查询应用或一个图片画廊。在实践中遇到的问题会促使你更深入地理解概念。
现代前端世界的大门已经为你打开。勇敢地去创造吧!
-