【前端】React框架详解:组件化开发与现代前端实践
前言
React是由Facebook(现Meta)开发的用于构建用户界面的JavaScript库,自2013年开源以来,已经成为最受欢迎的前端框架之一。React引入了组件化开发、虚拟DOM、单向数据流等革命性概念,彻底改变了前端开发的方式。本文将深入介绍React的核心概念、特性、最佳实践以及生态系统,帮助开发者全面掌握React开发技能。
一、React基础概念
(一)React的设计理念
1. 组件化思想
React将UI拆分为独立、可复用的组件,每个组件管理自己的状态和逻辑。
// 简单的组件示例
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用组件
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
<Welcome name="Charlie" />
</div>
);
}
2. 声明式编程
React采用声明式编程范式,开发者只需描述UI应该是什么样子,而不需要关心如何操作DOM。
// 声明式:描述UI状态
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
{todo.text}
</li>
))}
</ul>
);
}
3. 单向数据流
数据从父组件流向子组件,通过props传递,保证了数据流的可预测性。
// 父组件
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<Child count={count} onIncrement={() => setCount(count + 1)} />
</div>
);
}
// 子组件
function Child({ count, onIncrement }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
}
(二)虚拟DOM
1. 虚拟DOM概念
虚拟DOM是React在内存中维护的一个轻量级的DOM表示,用于提高渲染性能。
// JSX会被转换为虚拟DOM对象
const element = <h1>Hello, World!</h1>;
// 等价于
const element = React.createElement(
'h1',
null,
'Hello, World!'
);
// 生成的虚拟DOM对象
{
type: 'h1',
props: {
children: 'Hello, World!'
}
}
2. Diff算法
React使用高效的Diff算法比较新旧虚拟DOM树,只更新发生变化的部分。
// React会智能地只更新变化的部分
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date()); // 只有时间文本会更新
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<div>
<h1>Current Time</h1> {/* 这部分不会重新渲染 */}
<p>{time.toLocaleTimeString()}</p> {/* 只有这部分会更新 */}
</div>
);
}
(三)JSX语法
1. JSX基础
JSX是JavaScript的语法扩展,允许在JavaScript中编写类似HTML的代码。
// JSX基本语法
function MyComponent() {
const name = 'React';
const isLoggedIn = true;
return (
<div className="container">
<h1>Welcome to {name}!</h1>
{isLoggedIn ? (
<p>You are logged in.</p>
) : (
<p>Please log in.</p>
)}
</div>
);
}
2. JSX表达式
function ExpressionExample() {
const user = {
firstName: 'John',
lastName: 'Doe'
};
const numbers = [1, 2, 3, 4, 5];
return (
<div>
{/* 对象属性访问 */}
<h1>Hello, {user.firstName} {user.lastName}!</h1>
{/* 函数调用 */}
<p>Current time: {new Date().toLocaleString()}</p>
{/* 数组渲染 */}
<ul>
{numbers.map(number => (
<li key={number}>{number * 2}</li>
))}
</ul>
{/* 条件渲染 */}
{user.firstName && <p>First name exists!</p>}
</div>
);
}
3. JSX属性
function AttributeExample() {
const imageUrl = 'https://example.com/image.jpg';
const altText = 'Example image';
const isDisabled = false;
return (
<div>
{/* 字符串属性 */}
<img src={imageUrl} alt={altText} />
{/* 布尔属性 */}
<button disabled={isDisabled}>Click me</button>
{/* 事件处理 */}
<button onClick={() => alert('Clicked!')}>Alert</button>
{/* CSS类名 */}
<div className="my-class">Styled div</div>
{/* 内联样式 */}
<p style={{ color: 'red', fontSize: '16px' }}>Red text</p>
</div>
);
}
二、React组件
(一)函数组件
1. 基本函数组件
// 简单函数组件
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用解构赋值
function Greeting({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old.</p>
</div>
);
}
// 箭头函数组件
const Greeting = ({ name, age }) => (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old.</p>
</div>
);
2. 带默认参数的组件
function Button({ text = 'Click me', onClick, disabled = false, variant = 'primary' }) {
const className = `btn btn-${variant} ${disabled ? 'disabled' : ''}`;
return (
<button
className={className}
onClick={onClick}
disabled={disabled}
>
{text}
</button>
);
}
// 使用组件
function App() {
return (
<div>
<Button text="Save" onClick={() => console.log('Saved')} />
<Button text="Cancel" variant="secondary" />
<Button disabled={true} />
</div>
);
}
(二)类组件(传统方式)
1. 基本类组件
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
// 带状态的类组件
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
2. 生命周期方法
class LifecycleExample extends Component {
constructor(props) {
super(props);
this.state = { data: null };
console.log('Constructor called');
}
componentDidMount() {
console.log('Component mounted');
// 组件挂载后执行,适合进行API调用
this.fetchData();
}
componentDidUpdate(prevProps, prevState) {
console.log('Component updated');
// 组件更新后执行
if (prevProps.userId !== this.props.userId) {
this.fetchData();
}
}
componentWillUnmount() {
console.log('Component will unmount');
// 组件卸载前执行,适合清理工作
if (this.timer) {
clearInterval(this.timer);
}
}
fetchData = async () => {
try {
const response = await fetch(`/api/users/${this.props.userId}`);
const data = await response.json();
this.setState({ data });
} catch (error) {
console.error('Error fetching data:', error);
}
}
render() {
console.log('Render called');
return (
<div>
{this.state.data ? (
<p>Data: {JSON.stringify(this.state.data)}</p>
) : (
<p>Loading...</p>
)}
</div>
);
}
}
(三)组件通信
1. Props传递
// 父组件向子组件传递数据
function Parent() {
const user = {
name: 'John Doe',
email: 'john@example.com',
avatar: 'https://example.com/avatar.jpg'
};
const handleUserUpdate = (updatedUser) => {
console.log('User updated:', updatedUser);
};
return (
<div>
<UserProfile
user={user}
onUpdate={handleUserUpdate}
isEditable={true}
/>
</div>
);
}
// 子组件接收props
function UserProfile({ user, onUpdate, isEditable }) {
const [isEditing, setIsEditing] = useState(false);
const handleSave = (newData) => {
onUpdate({ ...user, ...newData });
setIsEditing(false);
};
return (
<div className="user-profile">
<img src={user.avatar} alt={user.name} />
{isEditing ? (
<EditForm user={user} onSave={handleSave} />
) : (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
{isEditable && (
<button onClick={() => setIsEditing(true)}>Edit</button>
)}
</div>
)}
</div>
);
}
2. Context API
import React, { createContext, useContext, useState } from 'react';
// 创建Context
const ThemeContext = createContext();
const UserContext = createContext();
// Provider组件
function AppProvider({ children }) {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 自定义Hook
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
// 使用Context的组件
function Header() {
const { theme, toggleTheme } = useTheme();
const { user } = useUser();
return (
<header className={`header ${theme}`}>
<h1>My App</h1>
<div>
{user && <span>Welcome, {user.name}!</span>}
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
</div>
</header>
);
}
// 应用根组件
function App() {
return (
<AppProvider>
<Header />
{/* ... other components */}
</AppProvider>
);
}
# 三、React Hooks
## (一)常用Hooks
### 1. useState
```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>
<button onClick={() => setCount(prevCount => prevCount - 1)}>Decrement</button>
</div>
);
}
2. useEffect
import React, { useState, useEffect } from 'react';
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timerId = setInterval(() => {
setTime(new Date());
}, 1000);
// 清理函数
return () => {
clearInterval(timerId);
};
}, []); // 空依赖数组,只在挂载和卸载时执行
return <p>Current time: {time.toLocaleTimeString()}</p>;
}
3. useContext
// (参见上面的Context API示例)
4. useReducer
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 };
case 'reset':
return { count: action.payload };
default:
throw new Error();
}
}
function Counter() {
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>
<button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
</div>
);
}
(二)自定义Hooks
1. useFetch
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// 使用自定义Hook
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <div>{user.name}</div>;
}
2. useLocalStorage
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(storedValue));
} catch (error) {
console.error('Error setting localStorage:', error);
}
}, [key, storedValue]);
return [storedValue, setStoredValue];
}
// 使用自定义Hook
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div className={`app ${theme}`}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
四、状态管理
(一)Redux
1. Redux核心概念
- Store: 全局状态容器
- Action: 描述状态变化的对象
- Reducer: 根据action更新状态的纯函数
2. Redux Toolkit示例
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1; },
decrement: state => { state.value -= 1; }
}
});
export const { increment, decrement } = counterSlice.actions;
export const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
(二)Zustand
// store.js
import create from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 }))
}));
export default useStore;
// Counter.js
import React from 'react';
import useStore from './store';
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
五、路由管理 (React Router)
import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom';
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link> | <Link to="/users/1">User 1</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
</Router>
);
}
function UserProfile() {
const { userId } = useParams();
return <h2>User ID: {userId}</h2>;
}
六、性能优化
(一)React.memo
const MyComponent = React.memo(function MyComponent(props) {
// 只有在props变化时才会重新渲染
return <div>{props.data}</div>;
});
(二)useMemo 和 useCallback
function Parent() {
const [count, setCount] = useState(0);
const expensiveValue = useMemo(() => {
// 只有在依赖项变化时才重新计算
return computeExpensiveValue(count);
}, [count]);
const handleClick = useCallback(() => {
// 只有在依赖项变化时才重新创建函数
console.log('Button clicked');
}, []);
return <Child value={expensiveValue} onClick={handleClick} />;
}
七、测试
(一)Jest 和 React Testing Library
// Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter', () => {
render(<Counter />);
const button = screen.getByText(/increment/i);
fireEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
八、总结
React以其组件化、声明式和高效的特性,成为现代前端开发的主流选择。掌握React的核心概念、Hooks、状态管理和生态工具,将极大地提升开发效率和应用质量。
九、参考资料
function App() {
return (
{/* 其他组件 */}
);
}
# 三、React Hooks
## (一)基础Hooks
### 1. useState Hook
```jsx
import React, { useState } from 'react';
// 基本用法
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// 复杂状态管理
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const handleInputChange = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('User data:', user);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Name"
value={user.name}
onChange={(e) => handleInputChange('name', e.target.value)}
/>
<input
type="email"
placeholder="Email"
value={user.email}
onChange={(e) => handleInputChange('email', e.target.value)}
/>
<input
type="number"
placeholder="Age"
value={user.age}
onChange={(e) => handleInputChange('age', parseInt(e.target.value))}
/>
<button type="submit">Submit</button>
</form>
);
}
2. useEffect Hook
import React, { useState, useEffect } from 'react';
// 基本用法
function DocumentTitle() {
const [count, setCount] = useState(0);
// 每次渲染后执行
useEffect(() => {
document.title = `Count: ${count}`;
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// 带依赖数组的useEffect
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isCancelled = false;
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
const userData = await response.json();
if (!isCancelled) {
setUser(userData);
}
} catch (err) {
if (!isCancelled) {
setError(err.message);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchUser();
// 清理函数
return () => {
isCancelled = true;
};
}, [userId]); // 依赖数组
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
// 定时器示例
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
// 清理函数
return () => clearInterval(timer);
}, []); // 空依赖数组,只在挂载时执行
return (
<div>
<h2>Current Time</h2>
<p>{time.toLocaleTimeString()}</p>
</div>
);
}
3. useContext Hook
import React, { createContext, useContext, useState } from 'react';
// 创建多个Context
const AuthContext = createContext();
const SettingsContext = createContext();
// AuthProvider
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const login = async (credentials) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
setIsAuthenticated(true);
return { success: true };
} else {
return { success: false, error: 'Invalid credentials' };
}
} catch (error) {
return { success: false, error: error.message };
}
};
const logout = () => {
setUser(null);
setIsAuthenticated(false);
};
const value = {
user,
isAuthenticated,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// 自定义Hook
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
// 使用Context的组件
function LoginForm() {
const { login } = useAuth();
const [credentials, setCredentials] = useState({ username: '', password: '' });
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
const result = await login(credentials);
if (!result.success) {
setError(result.error);
}
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<input
type="text"
placeholder="Username"
value={credentials.username}
onChange={(e) => setCredentials({
...credentials,
username: e.target.value
})}
/>
<input
type="password"
placeholder="Password"
value={credentials.password}
onChange={(e) => setCredentials({
...credentials,
password: e.target.value
})}
/>
<button type="submit">Login</button>
</form>
);
}
function UserProfile() {
const { user, logout } = useAuth();
return (
<div>
<h2>Welcome, {user.name}!</h2>
<p>Email: {user.email}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
(二)高级Hooks
1. useReducer Hook
import React, { useReducer } 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
}
]
};
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 'SET_FILTER':
return {
...state,
filter: action.payload
};
default:
return state;
}
}
// 初始状态
const initialState = {
todos: [],
filter: 'all' // all, active, completed
};
// TodoApp组件
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const [inputValue, setInputValue] = useState('');
const addTodo = () => {
if (inputValue.trim()) {
dispatch({ type: 'ADD_TODO', payload: inputValue.trim() });
setInputValue('');
}
};
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
};
const deleteTodo = (id) => {
dispatch({ type: 'DELETE_TODO', payload: id });
};
const setFilter = (filter) => {
dispatch({ type: 'SET_FILTER', payload: filter });
};
// 过滤todos
const filteredTodos = state.todos.filter(todo => {
switch (state.filter) {
case 'active':
return !todo.completed;
case 'completed':
return todo.completed;
default:
return true;
}
});
return (
<div className="todo-app">
<h1>Todo App</h1>
{/* 添加todo */}
<div className="add-todo">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="Add a new todo..."
/>
<button onClick={addTodo}>Add</button>
</div>
{/* 过滤器 */}
<div className="filters">
<button
className={state.filter === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
All
</button>
<button
className={state.filter === 'active' ? 'active' : ''}
onClick={() => setFilter('active')}
>
Active
</button>
<button
className={state.filter === 'completed' ? 'active' : ''}
onClick={() => setFilter('completed')}
>
Completed
</button>
</div>
{/* Todo列表 */}
<ul className="todo-list">
{filteredTodos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
{/* 统计信息 */}
<div className="stats">
<p>Total: {state.todos.length}</p>
<p>Active: {state.todos.filter(t => !t.completed).length}</p>
<p>Completed: {state.todos.filter(t => t.completed).length}</p>
</div>
</div>
);
}
2. useMemo Hook
import React, { useState, useMemo } from 'react';
// 昂贵的计算函数
function expensiveCalculation(num) {
console.log('Performing expensive calculation...');
let result = 0;
for (let i = 0; i < num * 1000000; i++) {
result += i;
}
return result;
}
function ExpensiveComponent() {
const [count, setCount] = useState(1);
const [other, setOther] = useState(0);
// 使用useMemo缓存计算结果
const expensiveValue = useMemo(() => {
return expensiveCalculation(count);
}, [count]); // 只有count变化时才重新计算
return (
<div>
<h2>Expensive Calculation Example</h2>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<p>Other: {other}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<button onClick={() => setOther(other + 1)}>Increment Other</button>
</div>
);
}
// 复杂数据处理示例
function DataProcessor({ data, filters }) {
const [sortBy, setSortBy] = useState('name');
const [sortOrder, setSortOrder] = useState('asc');
// 使用useMemo优化数据处理
const processedData = useMemo(() => {
console.log('Processing data...');
// 过滤数据
let filtered = data.filter(item => {
return Object.keys(filters).every(key => {
if (!filters[key]) return true;
return item[key].toLowerCase().includes(filters[key].toLowerCase());
});
});
// 排序数据
filtered.sort((a, b) => {
const aValue = a[sortBy];
const bValue = b[sortBy];
if (sortOrder === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
return filtered;
}, [data, filters, sortBy, sortOrder]);
return (
<div>
<div className="controls">
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="name">Name</option>
<option value="age">Age</option>
<option value="email">Email</option>
</select>
<select value={sortOrder} onChange={(e) => setSortOrder(e.target.value)}>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</div>
<div className="data-list">
{processedData.map(item => (
<div key={item.id} className="data-item">
<h3>{item.name}</h3>
<p>Age: {item.age}</p>
<p>Email: {item.email}</p>
</div>
))}
</div>
</div>
);
}
3. useCallback Hook
import React, { useState, useCallback, memo } from 'react';
// 子组件(使用memo优化)
const ChildComponent = memo(({ onButtonClick, value }) => {
console.log('ChildComponent rendered');
return (
<div>
<p>Value: {value}</p>
<button onClick={onButtonClick}>Click me</button>
</div>
);
});
// 父组件
function ParentComponent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);
// 使用useCallback缓存函数
const handleButtonClick = useCallback(() => {
console.log('Button clicked!');
setCount(prevCount => prevCount + 1);
}, []); // 空依赖数组,函数永远不会重新创建
// 带依赖的useCallback
const handleSpecialClick = useCallback(() => {
console.log(`Special click with count: ${count}`);
}, [count]); // 只有count变化时才重新创建函数
return (
<div>
<h2>useCallback Example</h2>
<p>Count: {count}</p>
<p>Other: {other}</p>
<button onClick={() => setOther(other + 1)}>Increment Other</button>
{/* 子组件不会因为other的变化而重新渲染 */}
<ChildComponent
onButtonClick={handleButtonClick}
value={count}
/>
<button onClick={handleSpecialClick}>Special Click</button>
</div>
);
}
// 复杂示例:表单处理
function FormWithCallback() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
// 使用useCallback优化输入处理函数
const handleInputChange = useCallback((field) => {
return (event) => {
setFormData(prevData => ({
...prevData,
[field]: event.target.value
}));
};
}, []);
// 提交处理函数
const handleSubmit = useCallback((event) => {
event.preventDefault();
console.log('Form submitted:', formData);
// 这里可以发送数据到服务器
}, [formData]);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Name"
value={formData.name}
onChange={handleInputChange('name')}
/>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={handleInputChange('email')}
/>
<textarea
placeholder="Message"
value={formData.message}
onChange={handleInputChange('message')}
/>
<button type="submit">Submit</button>
</form>
);
}
(三)自定义Hooks
1. 基本自定义Hook
import { useState, useEffect } from 'react';
// 自定义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(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
// 设置值的函数
const setValue = (value) => {
try {
// 允许value是一个函数,用于函数式更新
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
};
return [storedValue, setValue];
}
// 使用自定义Hook
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'en');
return (
<div>
<h2>Settings</h2>
<div>
<label>
Theme:
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
</div>
<div>
<label>
Language:
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="zh">中文</option>
<option value="es">Español</option>
</select>
</label>
</div>
</div>
);
}
2. 数据获取Hook
import { useState, useEffect, useCallback } from 'react';
// 自定义Hook:数据获取
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url, options);
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);
}
}, [url, JSON.stringify(options)]);
useEffect(() => {
fetchData();
}, [fetchData]);
const refetch = useCallback(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch };
}
// 使用数据获取Hook
function UserList() {
const { data: users, loading, error, refetch } = useFetch('/api/users');
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h2>Users</h2>
<button onClick={refetch}>Refresh</button>
<ul>
{users?.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
// 带参数的数据获取Hook
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!userId) {
setUser(null);
return;
}
let isCancelled = false;
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('User not found');
}
const userData = await response.json();
if (!isCancelled) {
setUser(userData);
}
} catch (err) {
if (!isCancelled) {
setError(err.message);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchUser();
return () => {
isCancelled = true;
};
}, [userId]);
return { user, loading, error };
}
3. 表单处理Hook
import { useState, useCallback } from 'react';
// 自定义Hook:表单处理
function useForm(initialValues, validationRules = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
// 验证单个字段
const validateField = useCallback((name, value) => {
const rules = validationRules[name];
if (!rules) return '';
for (const rule of rules) {
const error = rule(value, values);
if (error) return error;
}
return '';
}, [validationRules, values]);
// 验证所有字段
const validateAll = useCallback(() => {
const newErrors = {};
Object.keys(validationRules).forEach(name => {
const error = validateField(name, values[name]);
if (error) {
newErrors[name] = error;
}
});
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}, [validationRules, values, validateField]);
// 处理输入变化
const handleChange = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// 如果字段已经被触摸过,立即验证
if (touched[name]) {
const error = validateField(name, value);
setErrors(prev => ({ ...prev, [name]: error }));
}
}, [touched, validateField]);
// 处理字段失焦
const handleBlur = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
const error = validateField(name, values[name]);
setErrors(prev => ({ ...prev, [name]: error }));
}, [values, validateField]);
// 重置表单
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
}, [initialValues]);
// 提交表单
const handleSubmit = useCallback((onSubmit) => {
return (event) => {
event.preventDefault();
// 标记所有字段为已触摸
const allTouched = Object.keys(validationRules).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {});
setTouched(allTouched);
// 验证所有字段
if (validateAll()) {
onSubmit(values);
}
};
}, [values, validationRules, validateAll]);
return {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
reset,
isValid: Object.keys(errors).length === 0
};
}
// 验证规则
const required = (message = 'This field is required') => (value) => {
return !value || value.trim() === '' ? message : '';
};
const email = (message = 'Invalid email format') => (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return value && !emailRegex.test(value) ? message : '';
};
const minLength = (min, message) => (value) => {
return value && value.length < min ? message || `Minimum length is ${min}` : '';
};
// 使用表单Hook
function ContactForm() {
const {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
reset,
isValid
} = useForm(
{
name: '',
email: '',
message: ''
},
{
name: [required('Name is required')],
email: [
required('Email is required'),
email('Please enter a valid email')
],
message: [
required('Message is required'),
minLength(10, 'Message must be at least 10 characters')
]
}
);
const onSubmit = (formData) => {
console.log('Form submitted:', formData);
// 这里可以发送数据到服务器
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-group">
<label htmlFor="name">Name:</label>
<input
id="name"
type="text"
value={values.name}
onChange={(e) => handleChange('name', e.target.value)}
onBlur={() => handleBlur('name')}
className={errors.name && touched.name ? 'error' : ''}
/>
{errors.name && touched.name && (
<span className="error-message">{errors.name}</span>
)}
</div>
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
id="email"
type="email"
value={values.email}
onChange={(e) => handleChange('email', e.target.value)}
onBlur={() => handleBlur('email')}
className={errors.email && touched.email ? 'error' : ''}
/>
{errors.email && touched.email && (
<span className="error-message">{errors.email}</span>
)}
</div>
<div className="form-group">
<label htmlFor="message">Message:</label>
<textarea
id="message"
value={values.message}
onChange={(e) => handleChange('message', e.target.value)}
onBlur={() => handleBlur('message')}
className={errors.message && touched.message ? 'error' : ''}
/>
{errors.message && touched.message && (
<span className="error-message">{errors.message}</span>
)}
</div>
<div className="form-actions">
<button type="submit" disabled={!isValid}>Submit</button>
<button type="button" onClick={reset}>Reset</button>
</div>
</form>
);
}
四、状态管理
(一)React内置状态管理
1. 组件状态提升
import React, { useState } from 'react';
// 子组件
function ProductCard({ product, onAddToCart, onRemoveFromCart, cartQuantity }) {
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<div className="cart-controls">
<button
onClick={() => onRemoveFromCart(product.id)}
disabled={cartQuantity === 0}
>
-
</button>
<span>Quantity: {cartQuantity}</span>
<button onClick={() => onAddToCart(product.id)}>+</button>
</div>
</div>
);
}
// 购物车组件
function ShoppingCart({ cartItems, products, onUpdateQuantity, onRemoveItem }) {
const total = cartItems.reduce((sum, item) => {
const product = products.find(p => p.id === item.productId);
return sum + (product.price * item.quantity);
}, 0);
return (
<div className="shopping-cart">
<h2>Shopping Cart</h2>
{cartItems.length === 0 ? (
<p>Your cart is empty</p>
) : (
<>
{cartItems.map(item => {
const product = products.find(p => p.id === item.productId);
return (
<div key={item.productId} className="cart-item">
<span>{product.name}</span>
<span>${product.price} x {item.quantity}</span>
<button onClick={() => onRemoveItem(item.productId)}>
Remove
</button>
</div>
);
})}
<div className="cart-total">
<strong>Total: ${total.toFixed(2)}</strong>
</div>
</>
)}
</div>
);
}
// 主应用组件(状态提升到这里)
function ShoppingApp() {
const [products] = useState([
{ id: 1, name: 'Laptop', price: 999.99, image: '/laptop.jpg' },
{ id: 2, name: 'Phone', price: 599.99, image: '/phone.jpg' },
{ id: 3, name: 'Tablet', price: 399.99, image: '/tablet.jpg' }
]);
const [cartItems, setCartItems] = useState([]);
// 添加到购物车
const addToCart = (productId) => {
setCartItems(prevItems => {
const existingItem = prevItems.find(item => item.productId === productId);
if (existingItem) {
return prevItems.map(item =>
item.productId === productId
? { ...item, quantity: item.quantity + 1 }
: item
);
} else {
return [...prevItems, { productId, quantity: 1 }];
}
});
};
// 从购物车移除
const removeFromCart = (productId) => {
setCartItems(prevItems => {
const existingItem = prevItems.find(item => item.productId === productId);
if (existingItem && existingItem.quantity > 1) {
return prevItems.map(item =>
item.productId === productId
? { ...item, quantity: item.quantity - 1 }
: item
);
} else {
return prevItems.filter(item => item.productId !== productId);
}
});
};
// 完全移除商品
const removeItem = (productId) => {
setCartItems(prevItems =>
prevItems.filter(item => item.productId !== productId)
);
};
// 获取商品在购物车中的数量
const getCartQuantity = (productId) => {
const item = cartItems.find(item => item.productId === productId);
return item ? item.quantity : 0;
};
return (
<div className="shopping-app">
<h1>Online Store</h1>
<div className="app-layout">
<div className="products-section">
<h2>Products</h2>
<div className="products-grid">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={addToCart}
onRemoveFromCart={removeFromCart}
cartQuantity={getCartQuantity(product.id)}
/>
))}
</div>
</div>
<div className="cart-section">
<ShoppingCart
cartItems={cartItems}
products={products}
onRemoveItem={removeItem}
/>
</div>
</div>
</div>
);
}
2. Context + useReducer模式
import React, { createContext, useContext, useReducer } from 'react';
// 定义action类型
const ACTIONS = {
ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: 'REMOVE_FROM_CART',
UPDATE_QUANTITY: 'UPDATE_QUANTITY',
CLEAR_CART: 'CLEAR_CART',
SET_LOADING: 'SET_LOADING',
SET_ERROR: 'SET_ERROR'
};
// Reducer函数
function cartReducer(state, action) {
switch (action.type) {
case ACTIONS.ADD_TO_CART:
const existingItemIndex = state.items.findIndex(
item => item.id === action.payload.id
);
if (existingItemIndex >= 0) {
const updatedItems = [...state.items];
updatedItems[existingItemIndex].quantity += 1;
return {
...state,
items: updatedItems
};
} else {
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
}
case ACTIONS.REMOVE_FROM_CART:
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case ACTIONS.UPDATE_QUANTITY:
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
).filter(item => item.quantity > 0)
};
case ACTIONS.CLEAR_CART:
return {
...state,
items: []
};
case ACTIONS.SET_LOADING:
return {
...state,
loading: action.payload
};
case ACTIONS.SET_ERROR:
return {
...state,
error: action.payload,
loading: false
};
default:
return state;
}
}
// 初始状态
const initialState = {
items: [],
loading: false,
error: null
};
// 创建Context
const CartContext = createContext();
// Provider组件
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
// Action creators
const addToCart = (product) => {
dispatch({ type: ACTIONS.ADD_TO_CART, payload: product });
};
const removeFromCart = (productId) => {
dispatch({ type: ACTIONS.REMOVE_FROM_CART, payload: productId });
};
const updateQuantity = (productId, quantity) => {
dispatch({
type: ACTIONS.UPDATE_QUANTITY,
payload: { id: productId, quantity }
});
};
const clearCart = () => {
dispatch({ type: ACTIONS.CLEAR_CART });
};
// 计算总价
const totalPrice = state.items.reduce(
(total, item) => total + (item.price * item.quantity),
0
);
// 计算总数量
const totalItems = state.items.reduce(
(total, item) => total + item.quantity,
0
);
const value = {
...state,
totalPrice,
totalItems,
addToCart,
removeFromCart,
updateQuantity,
clearCart
};
return (
<CartContext.Provider value={value}>
{children}
</CartContext.Provider>
);
}
// 自定义Hook
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
}
// 使用Context的组件
function ProductCard({ product }) {
const { addToCart, items } = useCart();
const cartItem = items.find(item => item.id === product.id);
const quantity = cartItem ? cartItem.quantity : 0;
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<div className="cart-controls">
<button onClick={() => addToCart(product)}>
Add to Cart {quantity > 0 && `(${quantity})`}
</button>
</div>
</div>
);
}
function CartSummary() {
const { items, totalPrice, totalItems, clearCart } = useCart();
return (
<div className="cart-summary">
<h3>Cart Summary</h3>
<p>Items: {totalItems}</p>
<p>Total: ${totalPrice.toFixed(2)}</p>
{items.length > 0 && (
<button onClick={clearCart}>Clear Cart</button>
)}
</div>
);
}
// 主应用
function App() {
return (
<CartProvider>
<div className="app">
<h1>React Shopping App</h1>
<ProductList />
<CartSummary />
</div>
</CartProvider>
);
}
(二)第三方状态管理
1. Redux基础
// store/actions.js
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const SET_FILTER = 'SET_FILTER';
export const addTodo = (text) => ({
type: ADD_TODO,
payload: {
id: Date.now(),
text,
completed: false
}
});
export const toggleTodo = (id) => ({
type: TOGGLE_TODO,
payload: id
});
export const deleteTodo = (id) => ({
type: DELETE_TODO,
payload: id
});
export const setFilter = (filter) => ({
type: SET_FILTER,
payload: filter
});
// store/reducers.js
import { combineReducers } from 'redux';
import { ADD_TODO, TOGGLE_TODO, DELETE_TODO, SET_FILTER } from './actions';
const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [...state, action.payload];
case TOGGLE_TODO:
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
case DELETE_TODO:
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
};
const filterReducer = (state = 'all', action) => {
switch (action.type) {
case SET_FILTER:
return action.payload;
default:
return state;
}
};
export default combineReducers({
todos: todosReducer,
filter: filterReducer
});
// store/index.js
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
2. React-Redux使用
import React, { useState } from 'react';
import { Provider, useSelector, useDispatch } from 'react-redux';
import store from './store';
import { addTodo, toggleTodo, deleteTodo, setFilter } from './store/actions';
// TodoList组件
function TodoList() {
const todos = useSelector(state => state.todos);
const filter = useSelector(state => state.filter);
const dispatch = useDispatch();
// 过滤todos
const filteredTodos = todos.filter(todo => {
switch (filter) {
case 'active':
return !todo.completed;
case 'completed':
return todo.completed;
default:
return true;
}
});
return (
<ul className="todo-list">
{filteredTodos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
/>
<span>{todo.text}</span>
<button onClick={() => dispatch(deleteTodo(todo.id))}>
Delete
</button>
</li>
))}
</ul>
);
}
// AddTodo组件
function AddTodo() {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text.trim()));
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new todo..."
/>
<button type="submit">Add</button>
</form>
);
}
// FilterButtons组件
function FilterButtons() {
const filter = useSelector(state => state.filter);
const dispatch = useDispatch();
return (
<div className="filters">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => dispatch(setFilter('all'))}
>
All
</button>
<button
className={filter === 'active' ? 'active' : ''}
onClick={() => dispatch(setFilter('active'))}
>
Active
</button>
<button
className={filter === 'completed' ? 'active' : ''}
onClick={() => dispatch(setFilter('completed'))}
>
Completed
</button>
</div>
);
}
// 主应用
function TodoApp() {
return (
<div className="todo-app">
<h1>Redux Todo App</h1>
<AddTodo />
<FilterButtons />
<TodoList />
</div>
);
}
// 根组件
function App() {
return (
<Provider store={store}>
<TodoApp />
</Provider>
);
}
五、性能优化
(一)React.memo
import React, { memo, useState, useCallback } from 'react';
// 使用memo优化的子组件
const ExpensiveChild = memo(({ data, onUpdate }) => {
console.log('ExpensiveChild rendered');
// 模拟昂贵的计算
const expensiveValue = data.reduce((sum, item) => sum + item.value, 0);
return (
<div>
<h3>Expensive Child Component</h3>
<p>Calculated Value: {expensiveValue}</p>
<button onClick={() => onUpdate(Math.random())}>
Update Random Value
</button>
</div>
);
});
// 带自定义比较函数的memo
const UserCard = memo(({ user, onEdit }) => {
console.log('UserCard rendered for:', user.name);
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较函数
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name &&
prevProps.user.email === nextProps.user.email &&
prevProps.user.age === nextProps.user.age
);
});
// 父组件
function OptimizedParent() {
const [count, setCount] = useState(0);
const [data, setData] = useState([
{ id: 1, value: 10 },
{ id: 2, value: 20 },
{ id: 3, value: 30 }
]);
// 使用useCallback优化回调函数
const handleUpdate = useCallback((newValue) => {
setData(prevData =>
prevData.map(item =>
item.id === 1 ? { ...item, value: newValue } : item
)
);
}, []);
return (
<div>
<h2>Performance Optimization Example</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
{/* 这个组件不会因为count的变化而重新渲染 */}
<ExpensiveChild data={data} onUpdate={handleUpdate} />
</div>
);
}
(二)useMemo和useCallback
import React, { useState, useMemo, useCallback } from 'react';
function ExpensiveCalculation({ items, multiplier }) {
// 使用useMemo缓存昂贵的计算结果
const expensiveValue = useMemo(() => {
console.log('Calculating expensive value...');
return items.reduce((sum, item) => sum + item * multiplier, 0);
}, [items, multiplier]); // 只有当items或multiplier改变时才重新计算
// 使用useMemo缓存复杂对象
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return [...items].sort((a, b) => b - a);
}, [items]);
return (
<div>
<h3>Expensive Calculation</h3>
<p>Result: {expensiveValue}</p>
<p>Sorted Items: {sortedItems.join(', ')}</p>
</div>
);
}
function CallbackExample() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
// 使用useCallback缓存函数
const handleAddItem = useCallback(() => {
setItems(prevItems => [...prevItems, Math.floor(Math.random() * 100)]);
}, []); // 空依赖数组,函数永远不会改变
const handleRemoveItem = useCallback((index) => {
setItems(prevItems => prevItems.filter((_, i) => i !== index));
}, []); // 空依赖数组
// 这个函数依赖于count,所以当count改变时会重新创建
const handleItemClick = useCallback((item) => {
console.log(`Clicked item ${item}, current count: ${count}`);
}, [count]);
return (
<div>
<h2>useCallback Example</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<button onClick={handleAddItem}>Add Random Item</button>
<ExpensiveCalculation items={items} multiplier={2} />
<div>
<h3>Items:</h3>
{items.map((item, index) => (
<div key={index}>
<span onClick={() => handleItemClick(item)}>{item}</span>
<button onClick={() => handleRemoveItem(index)}>Remove</button>
</div>
))}
</div>
</div>
);
}
(三)虚拟化(Virtualization)
import React, { useState, useMemo } from 'react';
// 简单的虚拟列表实现
function VirtualList({ items, itemHeight = 50, containerHeight = 300 }) {
const [scrollTop, setScrollTop] = useState(0);
// 计算可见范围
const visibleRange = useMemo(() => {
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
return { startIndex, endIndex };
}, [scrollTop, itemHeight, containerHeight, items.length]);
// 可见的项目
const visibleItems = useMemo(() => {
return items.slice(visibleRange.startIndex, visibleRange.endIndex);
}, [items, visibleRange]);
const totalHeight = items.length * itemHeight;
const offsetY = visibleRange.startIndex * itemHeight;
return (
<div
style={{
height: containerHeight,
overflow: 'auto',
border: '1px solid #ccc'
}}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div
style={{
transform: `translateY(${offsetY}px)`,
position: 'absolute',
top: 0,
left: 0,
right: 0
}}
>
{visibleItems.map((item, index) => (
<div
key={visibleRange.startIndex + index}
style={{
height: itemHeight,
padding: '10px',
borderBottom: '1px solid #eee',
display: 'flex',
alignItems: 'center'
}}
>
Item {visibleRange.startIndex + index}: {item}
</div>
))}
</div>
</div>
</div>
);
}
// 使用虚拟列表
function VirtualListExample() {
// 生成大量数据
const items = useMemo(() => {
return Array.from({ length: 10000 }, (_, i) => `Data item ${i + 1}`);
}, []);
return (
<div>
<h2>Virtual List Example</h2>
<p>Rendering 10,000 items efficiently</p>
<VirtualList items={items} itemHeight={60} containerHeight={400} />
</div>
);
}
六、路由(React Router)
(一)基础路由
import React from 'react';
import {
BrowserRouter as Router,
Routes,
Route,
Link,
useNavigate,
useParams,
useLocation
} from 'react-router-dom';
// 页面组件
function Home() {
return (
<div>
<h2>Home Page</h2>
<p>Welcome to the home page!</p>
</div>
);
}
function About() {
return (
<div>
<h2>About Page</h2>
<p>This is the about page.</p>
</div>
);
}
function Contact() {
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
// 处理表单提交
alert('Message sent!');
// 编程式导航
navigate('/');
};
return (
<div>
<h2>Contact Page</h2>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Your name" required />
<textarea placeholder="Your message" required></textarea>
<button type="submit">Send Message</button>
</form>
</div>
);
}
// 用户详情页面(带参数)
function UserProfile() {
const { userId } = useParams();
const location = useLocation();
return (
<div>
<h2>User Profile</h2>
<p>User ID: {userId}</p>
<p>Current path: {location.pathname}</p>
<p>Search params: {location.search}</p>
</div>
);
}
// 导航组件
function Navigation() {
return (
<nav style={{ padding: '20px', borderBottom: '1px solid #ccc' }}>
<Link to="/" style={{ marginRight: '10px' }}>Home</Link>
<Link to="/about" style={{ marginRight: '10px' }}>About</Link>
<Link to="/contact" style={{ marginRight: '10px' }}>Contact</Link>
<Link to="/user/123" style={{ marginRight: '10px' }}>User Profile</Link>
</nav>
);
}
// 404页面
function NotFound() {
return (
<div>
<h2>404 - Page Not Found</h2>
<p>The page you're looking for doesn't exist.</p>
<Link to="/">Go back to home</Link>
</div>
);
}
// 主应用
function RouterApp() {
return (
<Router>
<div>
<Navigation />
<div style={{ padding: '20px' }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/user/:userId" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</div>
</Router>
);
}
(二)嵌套路由和路由守卫
import React, { createContext, useContext, useState } from 'react';
import {
BrowserRouter as Router,
Routes,
Route,
Link,
Outlet,
Navigate,
useLocation
} from 'react-router-dom';
// 认证上下文
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (username) => {
setUser({ username });
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
return useContext(AuthContext);
}
// 路由守卫组件
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
// 重定向到登录页面,并保存当前位置
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 登录页面
function Login() {
const { login } = useAuth();
const location = useLocation();
const [username, setUsername] = useState('');
const from = location.state?.from?.pathname || '/';
const handleSubmit = (e) => {
e.preventDefault();
if (username.trim()) {
login(username);
// 登录成功后重定向到之前的页面
window.location.href = from;
}
};
return (
<div>
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter username"
required
/>
<button type="submit">Login</button>
</form>
</div>
);
}
// 仪表板布局
function DashboardLayout() {
const { user, logout } = useAuth();
return (
<div>
<header style={{ padding: '10px', backgroundColor: '#f0f0f0' }}>
<span>Welcome, {user?.username}!</span>
<button onClick={logout} style={{ marginLeft: '10px' }}>Logout</button>
</header>
<nav style={{ padding: '10px', borderBottom: '1px solid #ccc' }}>
<Link to="/dashboard" style={{ marginRight: '10px' }}>Dashboard</Link>
<Link to="/dashboard/profile" style={{ marginRight: '10px' }}>Profile</Link>
<Link to="/dashboard/settings" style={{ marginRight: '10px' }}>Settings</Link>
</nav>
<main style={{ padding: '20px' }}>
<Outlet /> {/* 嵌套路由的内容会在这里渲染 */}
</main>
</div>
);
}
// 仪表板页面
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<p>This is the main dashboard page.</p>
</div>
);
}
function Profile() {
const { user } = useAuth();
return (
<div>
<h2>Profile</h2>
<p>Username: {user?.username}</p>
<p>This is your profile page.</p>
</div>
);
}
function Settings() {
return (
<div>
<h2>Settings</h2>
<p>Configure your application settings here.</p>
</div>
);
}
// 带认证的路由应用
function AuthenticatedApp() {
return (
<AuthProvider>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
}
>
{/* 嵌套路由 */}
<Route index element={<Dashboard />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>
</Router>
</AuthProvider>
);
}
七、测试
(一)单元测试
// Counter.js
import React, { useState } from 'react';
function Counter({ initialValue = 0 }) {
const [count, setCount] = useState(initialValue);
return (
<div>
<h2>Counter</h2>
<p data-testid="count-value">Count: {count}</p>
<button
data-testid="increment-button"
onClick={() => setCount(count + 1)}
>
Increment
</button>
<button
data-testid="decrement-button"
onClick={() => setCount(count - 1)}
>
Decrement
</button>
<button
data-testid="reset-button"
onClick={() => setCount(initialValue)}
>
Reset
</button>
</div>
);
}
export default Counter;
// Counter.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Counter from './Counter';
describe('Counter Component', () => {
test('renders with initial value', () => {
render(<Counter initialValue={5} />);
expect(screen.getByTestId('count-value')).toHaveTextContent('Count: 5');
});
test('increments count when increment button is clicked', () => {
render(<Counter />);
const incrementButton = screen.getByTestId('increment-button');
const countValue = screen.getByTestId('count-value');
fireEvent.click(incrementButton);
expect(countValue).toHaveTextContent('Count: 1');
fireEvent.click(incrementButton);
expect(countValue).toHaveTextContent('Count: 2');
});
test('decrements count when decrement button is clicked', () => {
render(<Counter initialValue={5} />);
const decrementButton = screen.getByTestId('decrement-button');
const countValue = screen.getByTestId('count-value');
fireEvent.click(decrementButton);
expect(countValue).toHaveTextContent('Count: 4');
});
test('resets count to initial value when reset button is clicked', () => {
render(<Counter initialValue={10} />);
const incrementButton = screen.getByTestId('increment-button');
const resetButton = screen.getByTestId('reset-button');
const countValue = screen.getByTestId('count-value');
// 增加计数
fireEvent.click(incrementButton);
fireEvent.click(incrementButton);
expect(countValue).toHaveTextContent('Count: 12');
// 重置
fireEvent.click(resetButton);
expect(countValue).toHaveTextContent('Count: 10');
});
});
(二)集成测试
// UserList.test.js
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import UserList from './UserList';
// 模拟API服务器
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
])
);
})
);
// 启动服务器
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('UserList Component', () => {
test('loads and displays users', async () => {
render(<UserList />);
// 检查加载状态
expect(screen.getByText('Loading...')).toBeInTheDocument();
// 等待用户数据加载
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
// 确保加载状态消失
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
test('handles API error', async () => {
// 模拟API错误
server.use(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.status(500));
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Error loading users')).toBeInTheDocument();
});
});
});
八、最佳实践
(一)组件设计原则
// ❌ 不好的做法:组件职责过多
function BadUserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 获取用户信息
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
// 获取用户帖子
fetch(`/api/users/${userId}/posts`)
.then(res => res.json())
.then(setPosts);
setLoading(false);
}, [userId]);
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
))}
</div>
</div>
);
}
// ✅ 好的做法:职责分离
function UserInfo({ user }) {
return (
<div className="user-info">
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
function PostList({ posts }) {
return (
<div className="post-list">
{posts.map(post => (
<PostItem key={post.id} post={post} />
))}
</div>
);
}
function PostItem({ post }) {
return (
<article className="post-item">
<h3>{post.title}</h3>
<p>{post.content}</p>
</article>
);
}
function GoodUserProfile({ userId }) {
const { user, loading: userLoading } = useUser(userId);
const { posts, loading: postsLoading } = useUserPosts(userId);
if (userLoading || postsLoading) {
return <LoadingSpinner />;
}
return (
<div className="user-profile">
<UserInfo user={user} />
<PostList posts={posts} />
</div>
);
}
// 自定义Hooks
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(user => {
setUser(user);
setLoading(false);
});
}, [userId]);
return { user, loading };
}
function useUserPosts(userId) {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${userId}/posts`)
.then(res => res.json())
.then(posts => {
setPosts(posts);
setLoading(false);
});
}, [userId]);
return { posts, loading };
}
(二)性能优化建议
// 1. 避免在渲染中创建对象和函数
// ❌ 不好的做法
function BadComponent({ items }) {
return (
<div>
{items.map(item => (
<div
key={item.id}
style={{ padding: '10px', margin: '5px' }} // 每次渲染都创建新对象
onClick={() => console.log(item.id)} // 每次渲染都创建新函数
>
{item.name}
</div>
))}
</div>
);
}
// ✅ 好的做法
const itemStyle = { padding: '10px', margin: '5px' }; // 提取到组件外部
function GoodComponent({ items, onItemClick }) {
return (
<div>
{items.map(item => (
<div
key={item.id}
style={itemStyle}
onClick={() => onItemClick(item.id)}
>
{item.name}
</div>
))}
</div>
);
}
// 2. 合理使用key
// ❌ 不好的做法
function BadList({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item.name}</li> // 使用索引作为key
))}
</ul>
);
}
// ✅ 好的做法
function GoodList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li> // 使用唯一ID作为key
))}
</ul>
);
}
// 3. 条件渲染优化
// ❌ 不好的做法
function BadConditionalRender({ showExpensive, data }) {
const expensiveComponent = <ExpensiveComponent data={data} />; // 总是创建
return (
<div>
{showExpensive && expensiveComponent}
</div>
);
}
// ✅ 好的做法
function GoodConditionalRender({ showExpensive, data }) {
return (
<div>
{showExpensive && <ExpensiveComponent data={data} />} {/* 只在需要时创建 */}
</div>
);
}
九、总结
React作为现代前端开发的核心框架,提供了强大而灵活的组件化开发模式。通过本笔记的学习,我们掌握了:
核心概念
- 组件化思想:将UI拆分为可复用的组件
- 声明式编程:描述UI应该是什么样子,而不是如何实现
- 单向数据流:数据从父组件流向子组件
- 虚拟DOM:提高渲染性能的关键技术
重要特性
- Hooks:函数组件的状态管理和副作用处理
- 状态管理:从组件状态到全局状态管理
- 性能优化:memo、useMemo、useCallback等优化技术
- 路由管理:单页应用的导航和页面管理
最佳实践
- 组件设计:单一职责、可复用、可测试
- 性能优化:避免不必要的渲染和计算
- 代码组织:清晰的文件结构和命名规范
- 测试策略:单元测试和集成测试
React生态系统丰富,学习曲线相对平缓,是前端开发者必须掌握的重要技能。随着React 18的发布和并发特性的引入,React将继续引领前端开发的发展方向。
参考资料: