您的专属前端开发专家,带您玩转 React 生态
大家好!作为一名资深前端开发专家,我将带领各位“小白”踏上 React 的学习之旅。本教程将从最基础的概念开始,逐步深入到高级特性、性能优化和最佳实践,旨在为您提供一个**全面、易懂且充满实战指导**的 React 学习路径。
无论您是前端新手,还是希望系统学习 React 的开发者,这份教程都将是您不可或缺的指南。让我们一起拥抱 React 的魅力吧!
在深入 React 之前,请确保您对以下前端基础有基本了解:
npm 或 yarn 用于安装和管理项目依赖。React 是一个由 Facebook(现 Meta)开发的 JavaScript 库,用于构建用户界面。它专注于 UI 层,因此常被称为“UI 库”而非“框架”。
// 命令式:一步步告诉浏览器怎么做
// document.getElementById('count').innerText = count;
// 声明式:只描述结果,React 负责实现
// <p>当前计数: {count}</p>
我们将使用 Vite 来快速启动一个 React 项目,这是目前推荐的现代化工具。
# 1. 确保安装 Node.js (建议 LTS 版本)
node -v
npm -v
# 2. 使用 Vite 创建 React 项目
npm create vite@latest my-react-app -- --template react-ts # 使用 TypeScript 模板
# 3. 进入项目目录并安装依赖
cd my-react-app
npm install
# 4. 启动开发服务器
npm run dev
# 5. 在浏览器中访问 http://localhost:5173 (或提示的端口)
编辑器配置: 强烈推荐使用 VS Code,并安装以下插件:
JSX (JavaScript XML) 是 React 的核心特性,它允许您在 JavaScript 文件中编写类似 HTML 的结构。它不是字符串,也不是真正的 HTML,而是 React.createElement 的语法糖。
const element = <h1>Hello, React!</h1>;
const name = "Alice";
const greeting = <p>Hello, {name}!</p>;
const sum = 10 + 20;
const result = <p>Sum: {sum}</p>;
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>欢迎回来!</h1>
) : (
<p>请登录。</p>
)}
</div>
);
}
// 也可以使用逻辑与 &&
function ShowMessage({ hasMessage }) {
return (
<div>
{hasMessage && <p>您有新消息!</p>}
</div>
);
}
function ItemList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li> // key 必须是唯一的
))}
</ul>
);
}
组件是 React 应用的基石。它们是独立、可复用、可组合的代码块。
// 定义一个简单的函数组件
function WelcomeMessage(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 在另一个组件中使用
function App() {
return (
<div>
<WelcomeMessage name="Alice" />
<WelcomeMessage name="Bob" />
</div>
);
}
import React from 'react';
class OldSchoolCounter extends React.Component {
render() {
return <h1>Class Component Example</h1>;
}
}
建议: 初学者应主要学习和使用函数组件和 Hooks,它们是当前和未来的主流。
// ChildComponent.jsx
function ChildComponent(props) {
return <p>我收到来自父组件的消息:{props.message}</p>;
}
// ParentComponent.jsx
function ParentComponent() {
const dataFromParent = "你好,子组件!";
return <ChildComponent message={dataFromParent} />;
}
组件内部可变的数据称为“状态”。当状态改变时,组件会重新渲染。
import React, { useState } from 'react';
function Counter() {
// 声明一个状态变量 `count`,初始值为 0
// `setCount` 是一个用于更新 `count` 的函数
const [count, setCount] = useState(0);
const increment = () => {
// setCount 会异步更新状态,并触发组件重新渲染
setCount(prevCount => prevCount + 1); // 推荐使用函数式更新
};
return (
<div>
<p>当前计数: {count}</p>
<button onClick={increment}>增加</button>
</div>
);
}
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 副作用代码:在组件挂载后(或依赖项变化后)启动定时器
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
// 清理函数:在组件卸载前(或依赖项变化导致副作用重新执行前)执行
// 类似于 componentDidUnmount 或 componentDidUpdate 的清理逻辑
return () => {
clearInterval(intervalId);
console.log('定时器已清理');
};
}, []); // 依赖数组为空数组 [],表示只在组件挂载和卸载时执行
return (
<div>
<p>已过去 {seconds} 秒</p>
</div>
);
}
React 事件处理与原生 DOM 事件类似,但有一些不同。
function MyButton() {
const handleClick = (event) => {
console.log('按钮被点击了!', event.target); // event 是 SyntheticEvent
};
return (
<button onClick={handleClick}>点击我</button>
);
}
function ItemList({ items }) {
const handleDelete = (id, event) => {
console.log(`删除 ID 为 ${id} 的项`, event.type); // event.type 是事件类型,如 'click'
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={(event) => handleDelete(item.id, event)}>删除</button>
{/* 或者使用 bind (不推荐用于函数组件,因为每次渲染会创建新函数) */}
{/* <button onClick={handleDelete.bind(null, item.id)}>删除 (bind)</button> */}
</li>
))}
</ul>
);
}
除了 `useState` 和 `useEffect`,React 还提供了更多强大的 Hooks 来处理复杂场景。
// 1. 创建 Context
const ThemeContext = React.createContext('light'); // 默认值
// 2. Provider 提供值 (在父组件或应用根组件)
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. Consumer 消费值 (在任何子组件)
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = useContext(ThemeContext); // 使用 useContext 钩子获取值
return (
<button style={{ background: theme === 'dark' ? 'black' : 'white', color: theme === 'dark' ? 'white' : 'black' }}>
当前主题: {theme}
</button>
);
}
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function CounterWithReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
import React, { useState, useCallback, memo } from 'react';
// 纯组件:只有当 props 改变时才重新渲染
const MyButton = memo(({ onClick, label }) => {
console.log(`Rendering MyButton: ${label}`);
return <button onClick={onClick}>{label}</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(0);
// 记忆化回调函数:只有当 `count` 改变时才重新创建 handleClick
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [count]); // 依赖项
return (
<div>
<p>Count: {count}</p>
<p>Other State: {otherState}</p>
<MyButton onClick={handleClick} label="增加计数" />
<button onClick={() => setOtherState(otherState + 1)}>更新其他状态</button>
</div>>
);
}
import React, { useState, useMemo } from 'react';
function ExpensiveCalculationComponent({ num }) {
const [count, setCount] = useState(0);
// 只有当 `num` 改变时才重新计算 expensiveValue
const expensiveValue = useMemo(() => {
console.log('Performing expensive calculation...');
let result = 0;
for (let i = 0; i < num * 1000000; i++) { // 模拟耗时计算
result += i;
}
return result;
}, [num]); // 依赖项
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(count + 1)}>增加 Count</button>
</div>
);
}
import React, { useRef, useEffect } from 'react';
function MyForm() {
const inputRef = useRef(null); // 创建一个 ref
useEffect(() => {
// 组件挂载后,自动聚焦输入框
inputRef.current.focus();
}, []);
const handleClick = () => {
alert(inputRef.current.value); // 访问 DOM 元素的值
};
return (
<div>
<input type="text" ref={inputRef} /> {/* 将 ref 关联到 DOM 元素 */}
<button onClick={handleClick}>获取输入框值</button>
</div>
);
}
// useCounter.js (自定义 Hook)
import { useState, useCallback } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => setCount(c => c + 1), []);
const decrement = useCallback(() => setCount(c => c - 1), []);
const reset = useCallback(() => setCount(initialValue), [initialValue]);
return { count, increment, decrement, reset };
}
// 在组件中使用自定义 Hook
function MyCounterComponent() {
const { count, increment, decrement, reset } = useCounter(10); // 可以传入初始值
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>重置</button>
</div>
);
}
在 React 应用中,组件之间需要互相传递数据。
// ChildComponent.jsx
function ChildComponent({ onDataFromChild }) {
const data = "这是来自子组件的数据!";
return <button onClick={() => onDataFromChild(data)}>发送数据给父组件</button>;
}
// ParentComponent.jsx
function ParentComponent() {
const handleChildData = (data) => {
console.log("父组件收到数据:", data);
};
return <ChildComponent onDataFromChild={handleChildData} />;
}
构建单页面应用 (SPA) 离不开路由。`React Router` 是 React 最流行的路由库。
npm install react-router-dom@6
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
import { BrowserRouter } from 'react-router-dom'; // 导入 BrowserRouter
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter> {/* 在根组件外包裹 BrowserRouter */}
<App />
</BrowserRouter>
</React.StrictMode>,
);
import { Routes, Route, Link, NavLink, useNavigate } from 'react-router-dom';
import Home from './pages/Home'; // 假设有 Home.jsx
import About from './pages/About'; // 假设有 About.jsx
import UserProfile from './pages/UserProfile'; // 假设有 UserProfile.jsx
import NotFound from './pages/NotFound'; // 假设有 NotFound.jsx
function App() {
const navigate = useNavigate(); // 编程式导航 Hook
const goToAbout = () => {
navigate('/about');
};
return (
<div>
<nav>
<ul>
<li>
<Link to="/">首页</Link>
</li>
<li>
<NavLink to="/about" activeClassName="active">关于我们</NavLink> {/* NavLink 激活时有特殊样式 */}
</li>
<li>
<Link to="/user/123">用户123</Link>
</li>
<li>
<button onClick={goToAbout}>编程式跳转到关于</button>
</li>
</ul>
</nav>
<Routes> {/* 定义路由规则的容器 */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:userId" element={<UserProfile />} /> {/* 路由参数 */}
<Route path="*" element={<NotFound />} /> {/* 404 页面,匹配所有未匹配的路径 */}
</Routes>
</div>
);
}
export default App;
// UserProfile.jsx
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams(); // 获取 URL 中的 userId 参数
return <h2>用户 ID: {userId}</h2>;
}
对于大型或复杂应用,仅仅依靠 `useState` 和 `Context` 可能不够。状态管理库应运而生。
npm install @reduxjs/toolkit react-redux
为 React 组件添加样式有多种方式。
/* MyComponent.module.css */
.title {
color: blue;
}
.button {
background-color: green;
}
import styles from './MyComponent.module.css';
function MyComponent() {
return (
<div>
<h1 className={styles.title}>我的标题</h1>
<button className={styles.button}>点击我</button>
</div>
);
}
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: hotpink;
color: white;
padding: 10px 20px;
border-radius: 5px;
&:hover {
background-color: palevioletred;
}
`;
function MyStyledComponent() {
return <StyledButton>点击我 (Styled)</StyledButton>;
}
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Tailwind Button
</button>
<p style={{ color: 'red', fontSize: '16px' }}>红色文本</p>
React 组件通常需要从后端 API 获取数据。
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // npm install axios
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
setUsers(response.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []); // 空依赖数组,只在组件挂载时执行一次
if (loading) return <p>加载中...</p>;
if (error) return <p>加载失败: {error.message}</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
npm install swr # 或 react-query
// 使用 SWR 简化数据请求
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
function Profile() {
const { data, error } = useSWR('/api/user/profile', fetcher);
if (error) return <div>加载失败</div>;
if (!data) return <div>加载中...</div>;
return <div>你好,{data.name}!</div>;
}
提升 React 应用性能,提供流畅的用户体验是高级开发者的必备技能。
const MyPureComponent = React.memo(function MyComponent({ name }) {
console.log('MyPureComponent rendered');
return <div>Hello, {name}</div>;
});
React.lazy, Suspense): 将应用代码分割成更小的块,只在需要时加载。这能减少初始加载时间。
import React, { lazy, Suspense } from 'react';
// 懒加载组件
const About = lazy(() => import('./pages/About'));
function App() {
return (
<div>
<Suspense fallback={<div>加载中...</div>}> {/* fallback 显示加载状态 */}
<About />
</Suspense>
</div>
);
}
React 应用中,JavaScript 错误可能导致整个应用崩溃。错误边界是 React 组件,它可以捕获其子组件树中 JavaScript 错误,记录错误信息,并显示备用 UI。
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
// 当子组件抛出错误时调用
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
// 捕获错误信息
componentDidCatch(error, errorInfo) {
// 您可以将错误日志上报到错误监控服务
console.error("Uncaught error:", error, errorInfo);
this.setState({ error, errorInfo });
}
render() {
if (this.state.hasError) {
// 您可以渲染任何自定义的降级 UI
return (
<div style={{ border: '1px solid red', padding: '20px', margin: '20px', backgroundColor: '#ffe6e6' }}>
<h2>噢,出错了!</h2>
<p>非常抱歉,应用发生了一个错误。</p>
{/* <details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details> */}
</div>
);
}
return this.props.children; // 正常渲染子组件
}
}
// 在应用中使用错误边界
function App() {
return (
<div>
<ErrorBoundary>
{/* 可能会抛出错误的组件 */}
<MyProblematicComponent />
</ErrorBoundary>
<p>这个组件不会受到上面错误的影响。</p>
</div>
);
}
// 假设这是一个会抛出错误的组件
function MyProblematicComponent() {
const [count, setCount] = React.useState(0);
if (count > 3) {
throw new Error('我故意抛出错误了!');
}
return (
<div>
<p>计数器: {count}</p>
<button onClick={() => setCount(count + 1)}>增加计数 (达到4会报错)</button>
</div>
);
}
编写测试代码是保证 React 应用质量和可维护性的重要环节。
npm install --save-dev jest @testing-library/react @testing-library/jest-dom @babel/preset-env @babel/preset-react babel-jest
# 如果使用 Vite,建议使用 Vitest,它是 Vite 原生支持的测试框架
npm install --save-dev vitest @testing-library/react @testing-library/jest-dom
// Counter.jsx
// import React, { useState } from 'react';
// function Counter() {
// const [count, setCount] = useState(0);
// return (
// <div>
// <p>Count: {count}</p>
// <button onClick={() => setCount(count + 1)}>Increment</button>
// </div>
// );
// }
// export default Counter;
// Counter.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { expect, test, describe } from 'vitest';
import Counter from './Counter';
describe('Counter Component', () => {
test('renders with initial count of 0', () => {
render(<Counter />);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
});
test('increments count when button is clicked', () => {
render(<Counter />);
const incrementButton = screen.getByRole('button', { name: /increment/i });
fireEvent.click(incrementButton);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
});
React 拥有庞大而活跃的生态系统,遵循最佳实践能帮助您编写更健壮、可维护的代码。
// 定义 Props 类型
interface WelcomeProps {
name: string;
age?: number; // 可选属性
}
// 使用 TypeScript 的函数组件
const Welcome: React.FC<WelcomeProps> = ({ name, age }) => {
return <h1>Hello, {name}! {age && `You are ${age} years old.`}</h1>;
};
恭喜您!通过这份详尽的教程,您已经掌握了 React 从入门到精通所需的各项核心技能和最佳实践。从 JSX 到 Hooks,从路由到状态管理,再到性能优化和测试,您现在已经具备了构建现代化、高质量 React 应用的能力。
**学习的道路永无止境:** React 生态系统发展迅速,持续学习新特性、探索新的库和工具是保持竞争力的关键。勇敢地去实践吧,将所学知识运用到实际项目中,通过动手解决问题来巩固和深化理解。
祝您在 React 的世界中编码愉快,创造出令人惊艳的用户体验!如果您在学习过程中有任何疑问,请随时提出。