React 全面深入解析:从基础到高级实战指南
一、React 的基础知识
1. 什么是 React?
React 是由 Facebook 开发并开源的一个用于构建用户界面的 JavaScript 库。自 2013 年发布以来,React 已经发展成为前端开发领域最受欢迎的技术之一。与传统的前端开发框架(如 Angular、Vue)不同,React 的核心思想是组件化开发。
React 的核心特点:
-
声明式编程:React 采用声明式范式,让代码更加可预测且易于调试
-
组件化架构:将复杂的 UI 拆分为独立、可复用的组件
-
虚拟 DOM:通过高效的差异算法优化性能
-
单向数据流:数据自上而下流动,保证应用状态的可预测性
-
Learn Once, Write Anywhere:React 可以用于 Web、移动端(React Native)、VR 等场景
React 的发展历程:
-
2013 年:React 首次发布,引入 JSX 和虚拟 DOM 概念
-
2015 年:React Native 发布,扩展至移动端开发
-
2018 年:React 16.3 引入新的生命周期方法
-
2019 年:React 16.8 推出 Hooks,彻底改变函数组件的开发方式
-
2020 年:React 17 作为过渡版本,为未来特性做准备
-
2022 年:React 18 发布,引入并发特性
2. React 的基本概念
a. 组件(Components)
组件是 React 应用的构建块,每个组件都封装了自己的结构、样式和行为。
类组件(Class Components):
import React, { Component } from 'react';
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
message: 'Hello, World!'
};
}
componentDidMount() {
console.log('组件已挂载');
}
render() {
return <h1>{this.state.message}</h1>;
}
}
export default Welcome;
函数组件(Function Components):
import React from 'react';
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 或使用箭头函数
const Welcome = (props) => {
return <h1>Hello, {props.name}!</h1>;
};
export default Welcome;
b. JSX(JavaScript XML)
JSX 是 React 的核心语法扩展,它允许我们在 JavaScript 中编写类似 HTML 的代码。
JSX 的基本规则:
// 1. 必须有一个根元素
const element = (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
// 2. 使用 className 代替 class
const element = <div className="container">内容</div>;
// 3. 使用驼峰命名法定义属性
const element = <input defaultValue="默认值" onClick={handleClick} />;
// 4. 必须闭合所有标签
const element = <img src="image.jpg" alt="描述" />;
// 5. 在 JSX 中嵌入 JavaScript 表达式
const name = '张三';
const element = <h1>Hello, {name}!</h1>;
// 6. 条件渲染
const element = (
<div>
{isLoggedIn ? <UserPanel /> : <LoginForm />}
</div>
);
// 7. 列表渲染
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>{number}</li>
);
c. 虚拟 DOM(Virtual DOM)
虚拟 DOM 是 React 性能优化的核心机制,它通过以下步骤工作:
-
生成虚拟 DOM:当组件状态变化时,React 会重新生成整个 UI 的虚拟 DOM 表示
-
差异比较(Diffing):React 比较新旧虚拟 DOM 的差异
-
最小化更新:只更新实际 DOM 中发生变化的部分
// 虚拟 DOM 的工作原理示例
class Counter extends Component {
state = { count: 0 };handleClick = () => {
this.setState({ count: this.state.count + 1 });
};render() {
return (
计数: {this.state.count}
<button onClick={this.handleClick}>增加</button>
);
}
}
3. React 开发环境搭建
使用 Create React App 创建项目
# 安装 Create React App
npm install -g create-react-app
# 创建新项目
npx create-react-app my-react-app
# 进入项目目录
cd my-react-app
# 启动开发服务器
npm start
项目结构说明
my-react-app/
├── public/
│ ├── index.html
│ └── favicon.ico
├── src/
│ ├── components/ # 组件目录
│ ├── styles/ # 样式文件
│ ├── utils/ # 工具函数
│ ├── App.js # 根组件
│ ├── index.js # 入口文件
│ └── index.css # 全局样式
├── package.json
└── README.md
二、React 的进阶概念
1. 状态(State)和属性(Props)
State 的深入理解
类组件中的 State:
class Counter extends Component {
constructor(props) {
super(props);
// 初始化状态
this.state = {
count: 0,
isActive: true
};
}
// 正确更新状态的方式
increment = () => {
this.setState(prevState => ({
count: prevState.count + 1
}));
};
// 批量更新
handleMultipleUpdates = () => {
this.setState({ count: 1 });
this.setState({ isActive: false });
// 等同于
this.setState({
count: 1,
isActive: false
});
};
render() {
return (
<div>
<p>当前计数: {this.state.count}</p>
<button onClick={this.increment}>增加</button>
<p>状态: {this.state.isActive ? '活跃' : '非活跃'}</p>
</div>
);
}
}
函数组件中的 State(使用 useState Hook):
import React, { useState } from 'react';
function Counter() {
// 使用 useState 定义状态
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
// 更新对象状态
const updateUser = () => {
setUser(prevUser => ({
...prevUser,
age: prevUser.age + 1
}));
};
// 异步更新的注意事项
const incrementTwice = () => {
setCount(count + 1);
setCount(count + 1); // 这里不会立即更新,还是基于原来的 count
// 正确的方式
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={incrementTwice}>增加两次</button>
</div>
);
}
Props 的深入理解
Props 的基本使用:
// 父组件
function App() {
const user = { name: '张三', age: 25 };
const numbers = [1, 2, 3, 4, 5];
return (
<div>
<UserCard
name={user.name}
age={user.age}
isVerified={true}
onLogin={() => console.log('用户登录')}
/>
<NumberList numbers={numbers} />
</div>
);
}
// 子组件 - 函数组件
function UserCard(props) {
return (
<div className="user-card">
<h2>{props.name}</h2>
<p>年龄: {props.age}</p>
{props.isVerified && <span>已验证</span>}
<button onClick={props.onLogin}>登录</button>
</div>
);
}
// 子组件 - 使用解构赋值
function NumberList({ numbers, title = "数字列表" }) {
return (
<div>
<h3>{title}</h3>
<ul>
{numbers.map(num => (
<li key={num}>{num}</li>
))}
</ul>
</div>
);
}
// Props 的默认值
NumberList.defaultProps = {
title: "默认标题"
};
// Props 的类型检查
import PropTypes from 'prop-types';
NumberList.propTypes = {
numbers: PropTypes.array.isRequired,
title: PropTypes.string
};
Props 的高级用法:
// Children 属性
function Container({ children, className }) {
return <div className={`container ${className}`}>{children}</div>;
}
function App() {
return (
<Container className="main">
<h1>标题</h1>
<p>内容</p>
</Container>
);
}
// 渲染属性模式(Render Props)
class DataProvider extends Component {
state = { data: null, loading: true };
componentDidMount() {
fetch('/api/data')
.then(response => response.json())
.then(data => this.setState({ data, loading: false }));
}
render() {
return this.props.children(this.state);
}
}
// 使用
<DataProvider>
{({ data, loading }) => (
loading ? <div>加载中...</div> : <div>{data}</div>
)}
</DataProvider>
2. 生命周期方法(Lifecycle Methods)
类组件的生命周期
class LifecycleDemo extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('1. constructor - 构造函数');
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps - 从 props 派生状态');
return null;
}
componentDidMount() {
console.log('4. componentDidMount - 组件挂载完成');
// 适合进行数据获取、订阅事件等操作
}
shouldComponentUpdate(nextProps, nextState) {
console.log('5. shouldComponentUpdate - 是否应该更新');
return true; // 返回 false 可以阻止重新渲染
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('6. getSnapshotBeforeUpdate - 获取更新前的快照');
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('7. componentDidUpdate - 组件更新完成');
// 适合在更新后执行操作
}
componentWillUnmount() {
console.log('8. componentWillUnmount - 组件即将卸载');
// 清理工作:取消订阅、清除定时器等
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log('3. render - 渲染');
return (
<div>
<p>计数: {this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
</div>
);
}
}
生命周期图示
挂载阶段:
constructor → getDerivedStateFromProps → render → componentDidMount
更新阶段:
getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
卸载阶段:
componentWillUnmount
3. 钩子(Hooks)
Hooks 是 React 16.8 引入的革命性特性,让函数组件能够使用状态和其他 React 特性。
基础 Hooks
useState:
import React, { useState } from 'react';
function Example() {
// 基本类型状态
const [count, setCount] = useState(0);
// 对象类型状态
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// 数组类型状态
const [items, setItems] = useState([]);
// 更新对象状态
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
// 更新数组状态
const addItem = (item) => {
setItems(prevItems => [...prevItems, item]);
};
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<input
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
placeholder="姓名"
/>
</div>
);
}
useEffect:
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [userId, setUserId] = useState(1);
// 1. 无依赖数组 - 每次渲染后都执行
useEffect(() => {
console.log('组件渲染完成');
});
// 2. 空依赖数组 - 仅在挂载时执行一次
useEffect(() => {
console.log('组件挂载完成');
// 清理函数
return () => {
console.log('组件即将卸载');
};
}, []);
// 3. 有依赖数组 - 依赖变化时执行
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setData(userData);
} catch (error) {
console.error('获取数据失败:', error);
} finally {
setLoading(false);
}
};
fetchData();
// 清理函数:取消请求或清理副作用
return () => {
// 取消请求的逻辑
};
}, [userId]); // 依赖数组
if (loading) return <div>加载中...</div>;
return (
<div>
<h1>用户信息</h1>
<p>姓名: {data.name}</p>
<button onClick={() => setUserId(userId + 1)}>下一个用户</button>
</div>
);
}
useContext:
import React, { createContext, useContext, useState } from 'react';
// 创建 Context
const ThemeContext = createContext();
const UserContext = createContext();
// 提供者组件
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: '张三', role: 'admin' });
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
<Header />
<MainContent />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 消费者组件
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
const { user } = useContext(UserContext);
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<header className={`header-${theme}`}>
<h1>欢迎, {user.name}</h1>
<button onClick={toggleTheme}>
切换主题: {theme === 'light' ? '暗色' : '亮色'}
</button>
</header>
);
}
function MainContent() {
const { theme } = useContext(ThemeContext);
return (
<main className={`main-${theme}`}>
<p>这是主要内容区域</p>
</main>
);
}
高级 Hooks
useReducer:
import React, { useReducer } from 'react';
// 初始状态
const initialState = {
count: 0,
history: []
};
// Reducer 函数
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1,
history: [...state.history, { type: 'INCREMENT', value: state.count + 1 }]
};
case 'DECREMENT':
return {
...state,
count: state.count - 1,
history: [...state.history, { type: 'DECREMENT', value: state.count - 1 }]
};
case 'RESET':
return initialState;
case 'SET_COUNT':
return {
...state,
count: action.payload,
history: [...state.history, { type: 'SET', value: action.payload }]
};
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div>
<p>当前计数: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>增加</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>减少</button>
<button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
<button onClick={() => dispatch({ type: 'SET_COUNT', payload: 10 })}>
设置为10
</button>
<h3>操作历史:</h3>
<ul>
{state.history.map((entry, index) => (
<li key={index}>{entry.type}: {entry.value}</li>
))}
</ul>
</div>
);
}
useMemo 和 useCallback:
import React, { useState, useMemo, useCallback } from 'react';
function ExpensiveCalculation({ number }) {
// 使用 useMemo 缓存计算结果
const result = useMemo(() => {
console.log('执行昂贵计算...');
let sum = 0;
for (let i = 0; i < number; i++) {
sum += i;
}
return sum;
}, [number]); // 只有当 number 变化时才重新计算
return <div>计算结果: {result}</div>;
}
function UserList({ users, onUserClick }) {
// 使用 useMemo 缓存过滤结果
const activeUsers = useMemo(() => {
return users.filter(user => user.isActive);
}, [users]);
return (
<ul>
{activeUsers.map(user => (
<li key={user.id} onClick={() => onUserClick(user)}>
{user.name}
</li>
))}
</ul>
);
}
function App() {
const [count, setCount] = useState(0);
const [users, setUsers] = useState([
{ id: 1, name: '张三', isActive: true },
{ id: 2, name: '李四', isActive: false },
{ id: 3, name: '王五', isActive: true }
]);
// 使用 useCallback 缓存函数
const handleUserClick = useCallback((user) => {
console.log('用户被点击:', user.name);
}, []); // 空依赖数组表示函数不会改变
const addUser = useCallback((name) => {
setUsers(prevUsers => [
...prevUsers,
{ id: Date.now(), name, isActive: true }
]);
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>计数: {count}</button>
<ExpensiveCalculation number={1000000} />
<UserList users={users} onUserClick={handleUserClick} />
<button onClick={() => addUser('新用户')}>添加用户</button>
</div>
);
}
自定义 Hooks:
import { useState, useEffect } from 'react';
// 自定义 Hook:数据获取
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error('网络响应不正常');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// 自定义 Hook:本地存储
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`读取 localStorage 键 "${key}" 时出错:`, error);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`设置 localStorage 键 "${key}" 时出错:`, error);
}
};
return [storedValue, setValue];
}
// 使用自定义 Hooks
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
const [theme, setTheme] = useLocalStorage('theme', 'light');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div className={`profile-${theme}`}>
<h1>{user.name}</h1>
<p>邮箱: {user.email}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</div>
);
}
4. 事件处理(Event Handling)
function EventHandling() {
const [form, setForm] = useState({
username: '',
password: '',
rememberMe: false
});
// 基本事件处理
const handleClick = (event) => {
event.preventDefault();
console.log('按钮被点击了', event);
};
// 输入处理
const handleInputChange = (event) => {
const { name, value, type, checked } = event.target;
setForm(prevForm => ({
...prevForm,
[name]: type === 'checkbox' ? checked : value
}));
};
// 表单提交
const handleSubmit = (event) => {
event.preventDefault();
console.log('表单提交:', form);
};
// 键盘事件
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
console.log('Enter 键被按下');
}
};
// 合成事件池(React 17 之前)
const handleEventPool = (event) => {
// React 17 之前需要持久化事件对象
event.persist();
setTimeout(() => {
console.log(event.type);
}, 1000);
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={form.username}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
placeholder="用户名"
/>
<input
name="password"
type="password"
value={form.password}
onChange={handleInputChange}
placeholder="密码"
/>
<label>
<input
name="rememberMe"
type="checkbox"
checked={form.rememberMe}
onChange={handleInputChange}
/>
记住我
</label>
<button type="submit" onClick={handleClick}>
登录
</button>
</form>
);
}
三、React 的高级应用
1. 状态管理(State Management)
a. Redux 状态管理
Redux 基础设置:
// store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
// Action Types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const SET_USER = 'SET_USER';
// Action Creators
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
export const setUser = (user) => ({ type: SET_USER, payload: user });
// 异步 Action
export const fetchUser = (userId) => {
return async (dispatch) => {
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
dispatch(setUser(user));
} catch (error) {
console.error('获取用户失败:', error);
}
};
};
// Reducers
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
const userReducer = (state = null, action) => {
switch (action.type) {
case SET_USER:
return action.payload;
default:
return state;
}
};
const rootReducer = combineReducers({
counter: counterReducer,
user: userReducer
});
// 创建 Store
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
React-Redux 连接:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// Counter.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './store';
function Counter({ count, increment, decrement }) {
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
</div>
);
}
const mapStateToProps = (state) => ({
count: state.counter
});
const mapDispatchToProps = {
increment,
decrement
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
Redux Toolkit(现代 Redux):
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state) => state + 1,
decrement: (state) => state - 1,
incrementByAmount: (state, action) => state + action.payload
}
});
const userSlice = createSlice({
name: 'user',
initialState: null,
reducers: {
setUser: (state, action) => action.payload,
clearUser: () => null
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const { setUser, clearUser } = userSlice.actions;
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
user: userSlice.reducer
}
});
export default store;
// 在组件中使用
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store';
function Counter() {
const count = useSelector(state => state.counter);
const dispatch = useDispatch();
return (
<div>
<p>计数: {count}</p>
<button onClick={() => dispatch(increment())}>增加</button>
<button onClick={() => dispatch(decrement())}>减少</button>
</div>
);
}
b. Context API 状态管理
创建 Context:
// contexts/AppContext.js
import React, { createContext, useContext, useReducer } from 'react';
const AppContext = createContext();
// 初始状态
const initialState = {
user: null,
theme: 'light',
notifications: [],
loading: false
};
// Reducer 函数
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'SET_THEME':
return { ...state, theme: action.payload };
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [...state.notifications, action.payload]
};
case 'SET_LOADING':
return { ...state, loading: action.payload };
default:
return state;
}
}
// Provider 组件
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
// Action creators
const setUser = (user) => dispatch({ type: 'SET_USER', payload: user });
const setTheme = (theme) => dispatch({ type: 'SET_THEME', payload: theme });
const addNotification = (message) =>
dispatch({ type: 'ADD_NOTIFICATION', payload: { id: Date.now(), message } });
const setLoading = (loading) => dispatch({ type: 'SET_LOADING', payload: loading });
const value = {
...state,
setUser,
setTheme,
addNotification,
setLoading
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// 自定义 Hook
export function useApp() {
const context = useContext(AppContext);
if (!context) {
throw new Error('useApp 必须在 AppProvider 内部使用');
}
return context;
}
使用 Context:
// App.js
import React from 'react';
import { AppProvider } from './contexts/AppContext';
import Header from './components/Header';
import MainContent from './components/MainContent';
import NotificationCenter from './components/NotificationCenter';
function App() {
return (
<AppProvider>
<div className="app">
<Header />
<MainContent />
<NotificationCenter />
</div>
</AppProvider>
);
}
// Header.js
import React from 'react';
import { useApp } from '../contexts/AppContext';
function Header() {
const { user, theme, setTheme } = useApp();
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<header className={`header-${theme}`}>
<h1>我的应用</h1>
{user && <span>欢迎, {user.name}</span>}
<button onClick={toggleTheme}>
{theme === 'light' ? '切换到暗色主题' : '切换到亮色主题'}
</button>
</header>
);
}
2. 路由(Routing)
React Router 基础:
// App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import User from './pages/User';
import NotFound from './pages/NotFound';
function App() {
return (
<Router>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/user/123">用户123</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<User />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
}
// User.js
import React from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
function User() {
const { id } = useParams();
const navigate = useNavigate();
const location = useLocation();
const goBack = () => {
navigate(-1); // 返回上一页
};
const goToAbout = () => {
navigate('/about', { replace: true }); // 替换当前历史记录
};
return (
<div>
<h1>用户页面</h1>
<p>用户ID: {id}</p>
<p>当前路径: {location.pathname}</p>
<button onClick={goBack}>返回</button>
<button onClick={goToAbout}>关于页面</button>
</div>
);
}
路由保护:
// ProtectedRoute.js
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) {
return <div>加载中...</div>;
}
if (!user) {
// 重定向到登录页面,并保存当前路径
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Routes>
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
</Routes>
3. 异步数据处理
数据获取模式:
import React, { useState, useEffect } from 'react';
function DataFetching() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []); // 空依赖数组表示只在挂载时执行
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!data) return <div>暂无数据</div>;
return (
<div>
<h1>数据列表</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
优化数据获取:
import React, { useState, useEffect, useRef } from 'react';
function OptimizedDataFetching() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const abortControllerRef = useRef(null);
useEffect(() => {
return () => {
// 组件卸载时中止请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
const fetchData = async (url) => {
if (abortControllerRef.current) {
abortControllerRef.current.abort(); // 中止之前的请求
}
abortControllerRef.current = new AbortController();
try {
setLoading(true);
setError(null);
const response = await fetch(url, {
signal: abortControllerRef.current.signal
});
if (!response.ok) throw new Error('请求失败');
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
abortControllerRef.current = null;
}
};
return (
<div>
<button onClick={() => fetchData('/api/data')}>获取数据</button>
{loading && <div>加载中...</div>}
{/* 渲染数据 */}
</div>
);
}
4. 性能优化(Performance Optimization)
React.memo:
import React, { memo } from 'react';
// 普通组件 - 每次父组件渲染都会重新渲染
function UserItem({ user, onEdit }) {
console.log('UserItem 渲染:', user.id);
return (
<li>
{user.name}
<button onClick={() => onEdit(user)}>编辑</button>
</li>
);
}
// 使用 memo 包装的组件 - 只有 props 变化时才会重新渲染
const OptimizedUserItem = memo(UserItem, (prevProps, nextProps) => {
// 自定义比较函数
return prevProps.user.id === nextProps.user.id &&
prevProps.onEdit === nextProps.onEdit;
});
// 使用 useCallback 优化函数引用
function UserList() {
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState(null);
// 使用 useCallback 缓存函数,避免不必要的重新渲染
const handleEdit = useCallback((user) => {
setSelectedUser(user);
}, []);
return (
<div>
{users.map(user => (
<OptimizedUserItem
key={user.id}
user={user}
onEdit={handleEdit}
/>
))}
</div>
);
}
useMemo 优化复杂计算:
import React, { useMemo } from 'react';
function ExpensiveComponent({ data, filter }) {
// 使用 useMemo 缓存计算结果
const filteredData = useMemo(() => {
console.log('执行过滤计算...');
return data.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [data, filter]); // 只有当 data 或 filter 变化时才重新计算
// 另一个使用 useMemo 的例子:配置对象
const config = useMemo(() => ({
maxItems: 10,
theme: 'dark',
onItemClick: () => console.log('项目被点击')
}), []); // 空依赖数组表示这个对象永远不会改变
return (
<div>
<h2>过滤结果 ({filteredData.length} 项)</h2>
<ul>
{filteredData.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
代码分割(懒加载):
import React, { lazy, Suspense } from 'react';
// 使用 lazy 进行代码分割
const LazyComponent = lazy(() => import('./LazyComponent'));
const AnotherLazyComponent = lazy(() => import('./AnotherLazyComponent'));
function App() {
const [showLazy, setShowLazy] = useState(false);
return (
<div>
<button onClick={() => setShowLazy(true)}>加载懒组件</button>
{showLazy && (
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
<AnotherLazyComponent />
</Suspense>
)}
</div>
);
}
// 路由级别的代码分割
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<Router>
<Suspense fallback={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
四、实际应用案例
1. 完整的 Todo 应用
import React, { useState, useReducer, useMemo, useCallback } from 'react';
// Reducer 函数
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false,
createdAt: new Date().toISOString()
}]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'EDIT_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id
? { ...todo, text: action.payload.text }
: todo
)
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
default:
return state;
}
}
// 初始状态
const initialState = {
todos: [],
filter: 'all' // all, active, completed
};
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const [inputValue, setInputValue] = useState('');
const [editingId, setEditingId] = useState(null);
const [editText, setEditText] = useState('');
// 添加待办事项
const addTodo = useCallback(() => {
if (inputValue.trim()) {
dispatch({ type: 'ADD_TODO', payload: inputValue.trim() });
setInputValue('');
}
}, [inputValue]);
// 切换完成状态
const toggleTodo = useCallback((id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
}, []);
// 删除待办事项
const deleteTodo = useCallback((id) => {
dispatch({ type: 'DELETE_TODO', payload: id });
}, []);
// 开始编辑
const startEdit = useCallback((todo) => {
setEditingId(todo.id);
setEditText(todo.text);
}, []);
// 保存编辑
const saveEdit = useCallback(() => {
if (editText.trim()) {
dispatch({
type: 'EDIT_TODO',
payload: { id: editingId, text: editText.trim() }
});
setEditingId(null);
setEditText('');
}
}, [editText, editingId]);
// 取消编辑
const cancelEdit = useCallback(() => {
setEditingId(null);
setEditText('');
}, []);
// 过滤待办事项
const filteredTodos = useMemo(() => {
switch (state.filter) {
case 'active':
return state.todos.filter(todo => !todo.completed);
case 'completed':
return state.todos.filter(todo => todo.completed);
default:
return state.todos;
}
}, [state.todos, state.filter]);
// 统计信息
const stats = useMemo(() => {
const total = state.todos.length;
const completed = state.todos.filter(todo => todo.completed).length;
const active = total - completed;
return { total, completed, active };
}, [state.todos]);
return (
<div className="todo-app">
<h1>Todo 应用</h1>
{/* 添加新待办事项 */}
<div className="add-todo">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="添加新的待办事项..."
/>
<button onClick={addTodo}>添加</button>
</div>
{/* 过滤选项 */}
<div className="filters">
<button
className={state.filter === 'all' ? 'active' : ''}
onClick={() => dispatch({ type: 'SET_FILTER', payload: 'all' })}
>
全部 ({stats.total})
</button>
<button
className={state.filter === 'active' ? 'active' : ''}
onClick={() => dispatch({ type: 'SET_FILTER', payload: 'active' })}
>
待完成 ({stats.active})
</button>
<button
className={state.filter === 'completed' ? 'active' : ''}
onClick={() => dispatch({ type: 'SET_FILTER', payload: 'completed' })}
>
已完成 ({stats.completed})
</button>
</div>
{/* 待办事项列表 */}
<ul className="todo-list">
{filteredTodos.map(todo => (
<li key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
{editingId === todo.id ? (
// 编辑模式
<div className="edit-mode">
<input
type="text"
value={editText}
onChange={(e) => setEditText(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') saveEdit();
if (e.key === 'Escape') cancelEdit();
}}
autoFocus
/>
<button onClick={saveEdit}>保存</button>
<button onClick={cancelEdit}>取消</button>
</div>
) : (
// 查看模式
<div className="view-mode">
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span
className="todo-text"
onDoubleClick={() => startEdit(todo)}
>
{todo.text}
</span>
<div className="actions">
<button onClick={() => startEdit(todo)}>编辑</button>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</div>
</div>
)}
</li>
))}
</ul>
{/* 空状态 */}
{filteredTodos.length === 0 && (
<div className="empty-state">
{state.filter === 'all' && '暂无待办事项'}
{state.filter === 'active' && '暂无待完成的待办事项'}
{state.filter === 'completed' && '暂无已完成的待办事项'}
</div>
)}
</div>
);
}
export default TodoApp;
2. 数据可视化仪表板
import React, { useState, useEffect, useMemo } from 'react';
import { useFetch } from '../hooks/useFetch';
function Dashboard() {
const [timeRange, setTimeRange] = useState('week');
const { data: stats, loading, error } = useFetch(`/api/stats?range=${timeRange}`);
// 处理图表数据
const chartData = useMemo(() => {
if (!stats) return null;
return {
labels: stats.chartData.labels,
datasets: [
{
label: '用户活跃度',
data: stats.chartData.values,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2
}
]
};
}, [stats]);
if (loading) return <div className="loading">加载中...</div>;
if (error) return <div className="error">错误: {error}</div>;
if (!stats) return <div>暂无数据</div>;
return (
<div className="dashboard">
<div className="dashboard-header">
<h1>数据仪表板</h1>
<div className="time-range-selector">
<button
className={timeRange === 'day' ? 'active' : ''}
onClick={() => setTimeRange('day')}
>
今日
</button>
<button
className={timeRange === 'week' ? 'active' : ''}
onClick={() => setTimeRange('week')}
>
本周
</button>
<button
className={timeRange === 'month' ? 'active' : ''}
onClick={() => setTimeRange('month')}
>
本月
</button>
</div>
</div>
<div className="stats-grid">
<StatCard
title="总用户数"
value={stats.totalUsers}
change={stats.userGrowth}
icon="👥"
/>
<StatCard
title="活跃用户"
value={stats.activeUsers}
change={stats.activeGrowth}
icon="🔥"
/>
<StatCard
title="总收入"
value={`$${stats.revenue}`}
change={stats.revenueGrowth}
icon="💰"
/>
<StatCard
title="转化率"
value={`${stats.conversionRate}%`}
change={stats.conversionGrowth}
icon="📈"
/>
</div>
<div className="charts">
<div className="chart-container">
<h3>用户活跃趋势</h3>
{/* 这里可以集成 Chart.js 或其他图表库 */}
<div className="chart-placeholder">
{/* 实际项目中这里会渲染真实的图表 */}
<p>图表组件占位符</p>
<pre>{JSON.stringify(chartData, null, 2)}</pre>
</div>
</div>
</div>
</div>
);
}
function StatCard({ title, value, change, icon }) {
const isPositive = change >= 0;
return (
<div className="stat-card">
<div className="stat-header">
<span className="stat-icon">{icon}</span>
<span className="stat-title">{title}</span>
</div>
<div className="stat-value">{value}</div>
<div className={`stat-change ${isPositive ? 'positive' : 'negative'}`}>
{isPositive ? '↑' : '↓'} {Math.abs(change)}%
</div>
</div>
);
}
export default Dashboard;
五、React 最佳实践和常见模式
1. 组件设计原则
单一职责原则:
// 不好的做法:一个组件做太多事情
function UserProfile() {
// 这个组件同时处理用户信息、设置、历史记录...
return <div>...</div>;
}
// 好的做法:拆分为专注的组件
function UserProfile() {
return (
<div>
<UserInfo />
<UserSettings />
<UserHistory />
</div>
);
}
function UserInfo({ user }) {
return (
<div className="user-info">
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
组合优于继承:
// 使用组合模式
function Card({ children, className }) {
return <div className={`card ${className}`}>{children}</div>;
}
function UserCard({ user }) {
return (
<Card className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</Card>
);
}
function ProductCard({ product }) {
return (
<Card className="product-card">
<h3>{product.name}</h3>
<p>${product.price}</p>
</Card>
);
}
2. 错误边界(Error Boundaries)
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({
error: error,
errorInfo: errorInfo
});
// 可以在这里记录错误到错误报告服务
console.error('错误边界捕获到错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>出了点问题。</h2>
<details>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
<button onClick={() => this.setState({ hasError: false })}>
重试
</button>
</div>
);
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
3. 测试策略
// MyComponent.js
import React from 'react';
function MyComponent({ onClick, value }) {
return (
<div>
<button onClick={onClick}>点击我</button>
<span data-testid="value">{value}</span>
</div>
);
}
export default MyComponent;
// MyComponent.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import MyComponent from './MyComponent';
test('渲染组件并显示正确的值', () => {
render(<MyComponent value="测试值" />);
expect(screen.getByTestId('value')).toHaveTextContent('测试值');
});
test('点击按钮时调用 onClick', () => {
const handleClick = jest.fn();
render(<MyComponent onClick={handleClick} />);
fireEvent.click(screen.getByText('点击我'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
六、React 18 新特性
1. 并发特性(Concurrent Features)
import React, { useState, Suspense, useTransition } from 'react';
function SearchResults({ query }) {
if (query === '') {
return null;
}
// 模拟数据加载
const results = use(fetchResults(query));
return (
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
);
}
function SearchPage() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
// 紧急更新:立即更新输入框
setQuery(value);
// 非紧急更新:标记搜索结果更新可以中断
startTransition(() => {
setQuery(value);
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
/>
{/* 显示加载状态 */}
{isPending && <div>加载中...</div>}
<Suspense fallback={<div>搜索中...</div>}>
<SearchResults query={query} />
</Suspense>
</div>
);
}
// 注意:use 钩子目前还是实验性功能
2. 自动批处理(Automatic Batching)
// React 17 及之前:在事件处理程序外部的更新不会批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 17 会渲染两次
}, 1000);
// React 18:所有更新都会自动批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 18 只会渲染一次
}, 1000);
// 如果需要同步更新,可以使用 flushSync
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM 已更新
flushSync(() => {
setFlag(f => !f);
});
// DOM 再次更新
}
总结
React 作为一个强大而灵活的库,已经成为了现代 Web 开发的标准工具。通过本文的全面介绍,我们从基础概念到高级应用,从最佳实践到最新特性,系统地探索了 React 的方方面面。