React基础到进阶
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 (<div><p>计数: {this.state.count}</p><button onClick={this.handleClick}>增加</button></div>);}
}
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}><inputname="username"value={form.username}onChange={handleInputChange}onKeyPress={handleKeyPress}placeholder="用户名"/><inputname="password"type="password"value={form.password}onChange={handleInputChange}placeholder="密码"/><label><inputname="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 creatorsconst 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"><inputtype="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"><inputtype="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"><inputtype="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 的方方面面。