React组件系统
第三章 组件系统
React 的核心思想是组件化开发,将用户界面拆分为独立、可复用的组件,每个组件管理自己的状态和逻辑。本章将深入探讨 React 组件系统的各个方面。
3.1 组件基础概念
3.1.1 组件的定义与分类
组件的本质:
graph TD
A[React 组件] --> B[函数组件]
A --> C[类组件]
B --> D[无状态组件]
B --> E[有状态组件 - Hooks]
C --> F[有状态组件]
C --> G[生命周期方法]
H[组件特点] --> I[封装性]
H --> J[可复用性]
H --> K[组合性]
H --> L[单向数据流]
组件分类详解:
// 1. 无状态函数组件
const WelcomeMessage = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
{age && <p>Age: {age}</p>}
</div>
);
};
// 2. 有状态函数组件 (使用 Hooks)
const Counter = () => {
const [count, setCount] = useState(0);
const [isIncrementing, setIsIncrementing] = useState(false);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
};
// 3. 类组件
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
isEditing: false,
formData: { ...props.user }
};
}
handleEdit = () => {
this.setState({ isEditing: true });
};
handleSave = () => {
this.props.onSave(this.state.formData);
this.setState({ isEditing: false });
};
handleChange = (field, value) => {
this.setState(prevState => ({
formData: {
...prevState.formData,
[field]: value
}
}));
};
render() {
const { user } = this.props;
const { isEditing, formData } = this.state;
return (
<div className="user-profile">
{isEditing ? (
<div>
<input
value={formData.name}
onChange={(e) => this.handleChange('name', e.target.value)}
/>
<input
value={formData.email}
onChange={(e) => this.handleChange('email', e.target.value)}
/>
<button onClick={this.handleSave}>Save</button>
</div>
) : (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={this.handleEdit}>Edit</button>
</div>
)}
</div>
);
}
}
// 4. 高阶组件 (HOC)
const withLogging = (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
console.log(`${WrappedComponent.name} mounted`);
}
componentWillUnmount() {
console.log(`${WrappedComponent.name} unmounted`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const LoggedCounter = withLogging(Counter);
// 5. 渲染属性组件 (Render Props)
class MouseTracker extends React.Component {
state = {
x: 0,
y: 0
};
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
});
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
// 使用方式
const App = () => {
return (
<div>
<WelcomeMessage name="Alice" age={25} />
<Counter />
<UserProfile
user={{ name: 'Bob', email: 'bob@example.com' }}
onSave={(data) => console.log('Saved:', data)}
/>
<LoggedCounter />
<MouseTracker
render={({ x, y }) => (
<div>Mouse position: ({x}, {y})</div>
)}
/>
</div>
);
};
3.1.2 组件的设计原则
单一职责原则:
// ❌ 违反单一职责原则
const UserProfileAndSettings = ({ user }) => {
const [isEditing, setIsEditing] = useState(false);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState(true);
const [language, setLanguage] = useState('en');
return (
<div>
{/* 用户信息显示和编辑 */}
<div className="user-profile">
{isEditing ? (
<form>
<input defaultValue={user.name} />
<input defaultValue={user.email} />
</form>
) : (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)}
<button onClick={() => setIsEditing(!isEditing)}>
{isEditing ? 'Save' : 'Edit'}
</button>
</div>
{/* 设置面板 */}
<div className="settings">
<h4>Settings</h4>
<label>
Theme:
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label>
Notifications:
<input
type="checkbox"
checked={notifications}
onChange={(e) => setNotifications(e.target.checked)}
/>
</label>
<label>
Language:
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</label>
</div>
</div>
);
};
// ✅ 遵循单一职责原则
const UserProfile = ({ user, onEdit }) => {
return (
<div className="user-profile">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={onEdit}>Edit</button>
</div>
);
};
const UserEditForm = ({ user, onSave, onCancel }) => {
const [formData, setFormData] = useState(user);
const handleSubmit = (e) => {
e.preventDefault();
onSave(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
<input
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
<button type="submit">Save</button>
<button type="button" onClick={onCancel}>Cancel</button>
</form>
);
};
const SettingsPanel = ({ theme, notifications, language, onThemeChange, onNotificationsChange, onLanguageChange }) => {
return (
<div className="settings">
<h4>Settings</h4>
<label>
Theme:
<select value={theme} onChange={(e) => onThemeChange(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label>
Notifications:
<input
type="checkbox"
checked={notifications}
onChange={(e) => onNotificationsChange(e.target.checked)}
/>
</label>
<label>
Language:
<select value={language} onChange={(e) onLanguageChange(e.target.value)}>
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</label>
</div>
);
};
// 组合组件
const UserProfileAndSettings = ({ user }) => {
const [isEditing, setIsEditing] = useState(false);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState(true);
const [language, setLanguage] = useState('en');
const handleEdit = () => setIsEditing(true);
const handleSave = (userData) => {
// 保存用户数据
setIsEditing(false);
};
const handleCancel = () => setIsEditing(false);
return (
<div>
{isEditing ? (
<UserEditForm
user={user}
onSave={handleSave}
onCancel={handleCancel}
/>
) : (
<UserProfile user={user} onEdit={handleEdit} />
)}
<SettingsPanel
theme={theme}
notifications={notifications}
language={language}
onThemeChange={setTheme}
onNotificationsChange={setNotifications}
onLanguageChange={setLanguage}
/>
</div>
);
};
开闭原则:
// ✅ 可扩展的组件设计
const DataTable = ({
data,
columns,
onRowClick,
rowClassName,
emptyMessage = 'No data available'
}) => {
if (!data || data.length === 0) {
return <div className="empty-message">{emptyMessage}</div>;
}
return (
<table className="data-table">
<thead>
<tr>
{columns.map(column => (
<th key={column.key}>{column.title}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr
key={row.id || index}
onClick={() => onRowClick && onRowClick(row)}
className={rowClassName ? rowClassName(row, index) : ''}
>
{columns.map(column => (
<td key={column.key}>
{column.render ? column.render(row[column.dataIndex], row) : row[column.dataIndex]}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
// 使用示例 - 不同场景的扩展
const UserTable = ({ users, onUserClick }) => {
const userColumns = [
{
key: 'name',
title: 'Name',
dataIndex: 'name',
render: (text, record) => <strong>{text}</strong>
},
{
key: 'email',
title: 'Email',
dataIndex: 'email'
},
{
key: 'status',
title: 'Status',
dataIndex: 'status',
render: (status) => (
<span className={`status-${status}`}>
{status}
</span>
)
},
{
key: 'actions',
title: 'Actions',
render: (_, record) => (
<div>
<button onClick={(e) => {
e.stopPropagation();
console.log('Edit user:', record.id);
}}>
Edit
</button>
<button onClick={(e) => {
e.stopPropagation();
console.log('Delete user:', record.id);
}}>
Delete
</button>
</div>
)
}
];
return (
<DataTable
data={users}
columns={userColumns}
onRowClick={onUserClick}
rowClassName={(row) => row.status === 'inactive' ? 'inactive-row' : ''}
/>
);
};
const ProductTable = ({ products, onProductSelect }) => {
const productColumns = [
{
key: 'image',
title: 'Image',
dataIndex: 'image',
render: (url) => <img src={url} alt="Product" width="50" />
},
{
key: 'name',
title: 'Product Name',
dataIndex: 'name'
},
{
key: 'price',
title: 'Price',
dataIndex: 'price',
render: (price) => `$${price.toFixed(2)}`
},
{
key: 'inStock',
title: 'Availability',
dataIndex: 'inStock',
render: (inStock) => inStock ? '✓ In Stock' : '✗ Out of Stock'
}
];
return (
<DataTable
data={products}
columns={productColumns}
onRowClick={onProductSelect}
rowClassName={(row) => !row.inStock ? 'out-of-stock' : ''}
emptyMessage="No products found"
/>
);
};
3.2 组件通信
3.2.1 Props 传递与验证
Props 传递模式:
import React from 'react';
import PropTypes from 'prop-types';
// 基础 Props 传递
const Button = ({
text,
onClick,
variant = 'primary',
size = 'medium',
disabled = false,
icon,
loading = false,
children,
...restProps
}) => {
const baseClasses = 'btn';
const variantClasses = `btn-${variant}`;
const sizeClasses = `btn-${size}`;
const disabledClasses = disabled ? 'btn-disabled' : '';
const loadingClasses = loading ? 'btn-loading' : '';
return (
<button
className={`${baseClasses} ${variantClasses} ${sizeClasses} ${disabledClasses} ${loadingClasses}`}
onClick={onClick}
disabled={disabled || loading}
{...restProps}
>
{loading && <span className="spinner" />}
{icon && <span className="icon">{icon}</span>}
{children || text}
</button>
);
};
// PropTypes 验证
Button.propTypes = {
text: PropTypes.string,
onClick: PropTypes.func,
variant: PropTypes.oneOf(['primary', 'secondary', 'success', 'warning', 'danger']),
size: PropTypes.oneOf(['small', 'medium', 'large']),
disabled: PropTypes.bool,
icon: PropTypes.node,
loading: PropTypes.bool,
children: PropTypes.node
};
// 默认 Props
Button.defaultProps = {
variant: 'primary',
size: 'medium',
disabled: false,
loading: false
};
// TypeScript Props 定义
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text?: string;
onClick?: () => void;
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
icon?: React.ReactNode;
loading?: boolean;
}
const Button: React.FC<ButtonProps> = ({
text,
onClick,
variant = 'primary',
size = 'medium',
disabled = false,
icon,
loading = false,
children,
...restProps
}) => {
// 组件实现
};
复杂 Props 传递模式:
// 配置对象模式
const DataVisualization = ({ config }) => {
const {
type,
data,
dimensions,
colors,
interactions,
animations,
responsive,
theme
} = config;
// 解构简化了 Props 结构
return (
<div className="data-viz">
{/* 基于配置渲染可视化 */}
</div>
);
};
// 使用配置模式
const chartConfig = {
type: 'bar',
data: [...],
dimensions: {
width: 800,
height: 400,
margin: { top: 20, right: 20, bottom: 40, left: 40 }
},
colors: ['#007bff', '#28a745', '#ffc107', '#dc3545'],
interactions: {
hover: true,
click: true,
zoom: false
},
animations: {
duration: 300,
easing: 'ease-out'
},
responsive: true,
theme: 'light'
};
// Render Props 模式
const DataProvider = ({ children, endpoint, method = 'GET' }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(endpoint, { method });
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [endpoint, method]);
// 传递数据和方法给子组件
return children({ data, loading, error, refetch: fetchData });
};
// 使用 Render Props
const UserList = () => {
return (
<DataProvider endpoint="/api/users">
{({ data, loading, error, refetch }) => (
<div>
{loading && <div>Loading users...</div>}
{error && <div>Error: {error}</div>}
{data && (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
<button onClick={refetch}>Refresh</button>
</div>
)}
</DataProvider>
);
};
3.2.2 Context 深度传递
Context 基础使用:
import React, { createContext, useContext, useState, useEffect } from 'react';
// 1. 创建 Context
const AppContext = createContext();
// 2. Context Provider 组件
const AppProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// 模拟用户认证
useEffect(() => {
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
}, []);
const login = async (credentials) => {
try {
// 模拟 API 调用
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
const userData = await response.json();
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
};
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
const addNotification = (notification) => {
const id = Date.now();
setNotifications(prev => [...prev, { ...notification, id }]);
// 自动移除通知
setTimeout(() => {
setNotifications(prev => prev.filter(n => n.id !== id));
}, 5000);
};
const value = {
// 状态
user,
theme,
notifications,
// 方法
setUser,
setTheme,
login,
logout,
addNotification
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
};
// 3. 自定义 Hook
const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppContext must be used within AppProvider');
}
return context;
};
// 4. 专用 Context Hooks
const useAuth = () => {
const { user, login, logout } = useAppContext();
return { user, login, logout, isAuthenticated: !!user };
};
const useTheme = () => {
const { theme, setTheme } = useAppContext();
return { theme, setTheme, isDark: theme === 'dark' };
};
const useNotifications = () => {
const { notifications, addNotification } = useAppContext();
return { notifications, addNotification };
};
// 5. 使用示例
const Header = () => {
const { user, logout } = useAuth();
const { theme, setTheme } = useTheme();
return (
<header className="header">
<h1>My App</h1>
<nav>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
{theme === 'light' ? '🌙' : '☀️'}
</button>
{user ? (
<div>
<span>Welcome, {user.name}!</span>
<button onClick={logout}>Logout</button>
</div>
) : (
<LoginModal />
)}
</nav>
</header>
);
};
const LoginModal = () => {
const { login } = useAuth();
const [credentials, setCredentials] = useState({ username: '', password: '' });
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
const result = await login(credentials);
if (!result.success) {
setError(result.error);
}
setLoading(false);
};
return (
<form onSubmit={handleSubmit}>
<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" disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
{error && <div className="error">{error}</div>}
</form>
);
};
const NotificationSystem = () => {
const { notifications } = useNotifications();
return (
<div className="notification-container">
{notifications.map(notification => (
<div key={notification.id} className={`notification ${notification.type}`}>
{notification.message}
</div>
))}
</div>
);
};
// 应用根组件
const App = () => {
return (
<AppProvider>
<div className="app">
<Header />
<main>
<Dashboard />
</main>
<NotificationSystem />
</div>
</AppProvider>
);
};
分层 Context 设计:
// 分层 Context 示例
const AuthContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();
const SettingsContext = createContext();
// 专门的 Provider
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [permissions, setPermissions] = useState([]);
const login = async (credentials) => {
// 登录逻辑
};
const logout = () => {
setUser(null);
setPermissions([]);
};
const hasPermission = (permission) => {
return permissions.includes(permission);
};
return (
<AuthContext.Provider value={{
user,
permissions,
login,
logout,
hasPermission,
isAuthenticated: !!user
}}>
{children}
</AuthContext.Provider>
);
};
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const [customColors, setCustomColors] = useState({});
return (
<ThemeContext.Provider value={{
theme,
setTheme,
customColors,
setCustomColors,
isDark: theme === 'dark'
}}>
{children}
</ThemeContext.Provider>
);
};
// 组合 Provider
const AppProviders = ({ children }) => {
return (
<AuthProvider>
<ThemeProvider>
<NotificationProvider>
<SettingsProvider>
{children}
</SettingsProvider>
</NotificationProvider>
</ThemeProvider>
</AuthProvider>
);
};
// 使用分层 Context
const AdminPanel = () => {
const { hasPermission } = useContext(AuthContext);
const { theme } = useContext(ThemeContext);
if (!hasPermission('admin_access')) {
return <div>Access Denied</div>;
}
return (
<div className={`admin-panel theme-${theme}`}>
<h2>Admin Panel</h2>
<UserManagement />
<SystemSettings />
</div>
);
};
3.2.3 状态提升与回调
状态提升模式:
// 父组件管理共享状态
const TemperatureConverter = () => {
const [celsius, setCelsius] = useState('');
const [fahrenheit, setFahrenheit] = useState('');
// 摄氏度变化处理
const handleCelsiusChange = (value) => {
setCelsius(value);
if (value === '') {
setFahrenheit('');
} else {
const f = (parseFloat(value) * 9/5) + 32;
setFahrenheit(f.toFixed(2));
}
};
// 华氏度变化处理
const handleFahrenheitChange = (value) => {
setFahrenheit(value);
if (value === '') {
setCelsius('');
} else {
const c = (parseFloat(value) - 32) * 5/9;
setCelsius(c.toFixed(2));
}
};
return (
<div className="temperature-converter">
<h2>Temperature Converter</h2>
<div className="converter-container">
<CelsiusInput
value={celsius}
onChange={handleCelsiusChange}
/>
<div className="equals">=</div>
<FahrenheitInput
value={fahrenheit}
onChange={handleFahrenheitChange}
/>
</div>
</div>
);
};
const CelsiusInput = ({ value, onChange }) => {
return (
<div className="input-group">
<label>Celsius:</label>
<input
type="number"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Enter Celsius"
/>
<span>°C</span>
</div>
);
};
const FahrenheitInput = ({ value, onChange }) => {
return (
<div className="input-group">
<label>Fahrenheit:</label>
<input
type="number"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Enter Fahrenheit"
/>
<span>°F</span>
</div>
);
};
复杂状态的回调管理:
// 多个组件共享复杂状态
const ShoppingApp = () => {
const [cart, setCart] = useState([]);
const [products, setProducts] = useState([]);
const [filters, setFilters] = useState({
category: 'all',
priceRange: [0, 1000],
inStock: true
});
const [isLoading, setIsLoading] = useState(false);
// 购物车操作
const addToCart = (product) => {
setCart(prevCart => {
const existingItem = prevCart.find(item => item.id === product.id);
if (existingItem) {
return prevCart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prevCart, { ...product, quantity: 1 }];
});
};
const removeFromCart = (productId) => {
setCart(prevCart => prevCart.filter(item => item.id !== productId));
};
const updateQuantity = (productId, quantity) => {
if (quantity <= 0) {
removeFromCart(productId);
} else {
setCart(prevCart =>
prevCart.map(item =>
item.id === productId ? { ...item, quantity } : item
)
);
}
};
const clearCart = () => {
setCart([]);
};
// 过滤操作
const updateFilters = (newFilters) => {
setFilters(prev => ({ ...prev, ...newFilters }));
};
const filteredProducts = products.filter(product => {
if (filters.category !== 'all' && product.category !== filters.category) {
return false;
}
if (product.price < filters.priceRange[0] || product.price > filters.priceRange[1]) {
return false;
}
if (filters.inStock && !product.inStock) {
return false;
}
return true;
});
const cartTotal = cart.reduce((total, item) => total + (item.price * item.quantity), 0);
const cartItemCount = cart.reduce((total, item) => total + item.quantity, 0);
return (
<div className="shopping-app">
<header>
<h1>Shopping Store</h1>
<CartBadge count={cartItemCount} total={cartTotal} />
</header>
<main>
<aside>
<FilterPanel
filters={filters}
onFilterChange={updateFilters}
categories={[...new Set(products.map(p => p.category))]}
/>
</aside>
<section>
<ProductGrid
products={filteredProducts}
onAddToCart={addToCart}
isLoading={isLoading}
/>
</section>
</main>
<CartWidget
cart={cart}
onRemoveItem={removeFromCart}
onUpdateQuantity={updateQuantity}
onClearCart={clearCart}
/>
</div>
);
};
const CartBadge = ({ count, total }) => {
return (
<div className="cart-badge">
<span className="count">🛒 {count}</span>
<span className="total">${total.toFixed(2)}</span>
</div>
);
};
const FilterPanel = ({ filters, onFilterChange, categories }) => {
const [priceRange, setPriceRange] = useState(filters.priceRange);
const handlePriceRangeChange = (type, value) => {
const newRange = type === 'min'
? [value, priceRange[1]]
: [priceRange[0], value];
setPriceRange(newRange);
onFilterChange({ priceRange: newRange });
};
return (
<div className="filter-panel">
<h3>Filters</h3>
<div className="filter-group">
<label>Category:</label>
<select
value={filters.category}
onChange={(e) => onFilterChange({ category: e.target.value })}
>
<option value="all">All Categories</option>
{categories.map(category => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
</div>
<div className="filter-group">
<label>Price Range:</label>
<div>
<input
type="number"
placeholder="Min"
value={priceRange[0]}
onChange={(e) => handlePriceRangeChange('min', parseFloat(e.target.value) || 0)}
/>
<span>-</span>
<input
type="number"
placeholder="Max"
value={priceRange[1]}
onChange={(e) => handlePriceRangeChange('max', parseFloat(e.target.value) || 1000)}
/>
</div>
</div>
<div className="filter-group">
<label>
<input
type="checkbox"
checked={filters.inStock}
onChange={(e) => onFilterChange({ inStock: e.target.checked })}
/>
In Stock Only
</label>
</div>
</div>
);
};
const ProductGrid = ({ products, onAddToCart, isLoading }) => {
if (isLoading) {
return <div>Loading products...</div>;
}
if (products.length === 0) {
return <div>No products found matching your filters.</div>;
}
return (
<div className="product-grid">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={onAddToCart}
/>
))}
</div>
);
};
const ProductCard = ({ product, onAddToCart }) => {
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h4>{product.name}</h4>
<p className="price">${product.price.toFixed(2)}</p>
<p className="category">{product.category}</p>
{!product.inStock && <span className="out-of-stock">Out of Stock</span>}
<button
onClick={() => onAddToCart(product)}
disabled={!product.inStock}
>
{product.inStock ? 'Add to Cart' : 'Out of Stock'}
</button>
</div>
);
};
const CartWidget = ({ cart, onRemoveItem, onUpdateQuantity, onClearCart }) => {
if (cart.length === 0) {
return (
<div className="cart-widget empty">
<h3>Your Cart</h3>
<p>Your cart is empty</p>
</div>
);
}
const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
return (
<div className="cart-widget">
<div className="cart-header">
<h3>Your Cart</h3>
<button onClick={onClearCart} className="clear-cart">
Clear All
</button>
</div>
<div className="cart-items">
{cart.map(item => (
<CartItem
key={item.id}
item={item}
onRemove={onRemoveItem}
onUpdateQuantity={onUpdateQuantity}
/>
))}
</div>
<div className="cart-footer">
<div className="total">
<strong>Total: ${total.toFixed(2)}</strong>
</div>
<button className="checkout-btn">Checkout</button>
</div>
</div>
);
};
const CartItem = ({ item, onRemove, onUpdateQuantity }) => {
const handleQuantityChange = (newQuantity) => {
onUpdateQuantity(item.id, newQuantity);
};
return (
<div className="cart-item">
<img src={item.image} alt={item.name} className="item-image" />
<div className="item-details">
<h4>{item.name}</h4>
<p>${item.price.toFixed(2)}</p>
</div>
<div className="item-quantity">
<button
onClick={() => handleQuantityChange(item.quantity - 1)}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() => handleQuantityChange(item.quantity + 1)}
>
+
</button>
</div>
<div className="item-total">
${(item.price * item.quantity).toFixed(2)}
</div>
<button
onClick={() => onRemove(item.id)}
className="remove-item"
>
×
</button>
</div>
);
};
3.3 组件生命周期
3.3.1 类组件生命周期
生命周期方法详解:
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
// 1. 初始化状态
this.state = {
user: null,
loading: true,
error: null,
editMode: false,
formData: {}
};
// 绑定方法
this.handleEdit = this.handleEdit.bind(this);
this.handleSave = this.handleSave.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
// 2. 组件挂载前 - getDerivedStateFromProps (静态方法)
static getDerivedStateFromProps(nextProps, prevState) {
// 根据 props 更新 state
if (nextProps.userId !== prevState.userId) {
return {
userId: nextProps.userId,
loading: true,
user: null,
error: null
};
}
return null; // 不更新 state
}
// 3. 挂载前 - render 之前的最后一次更新 state 的机会
// 在 React 16.3 后已不推荐使用,被 getDerivedStateFromProps 替代
// 4. 挂载后 - 组件首次渲染后执行
componentDidMount() {
console.log('UserProfile mounted');
// 适合的操作:
// - 发起 API 请求
// - 设置定时器
// - 添加事件监听器
// - 操作 DOM
this.fetchUserData();
this.setupWindowResizeListener();
}
// 5. 更新前 - getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {
// 在更新前获取 DOM 信息,返回值会传递给 componentDidUpdate
if (prevState.editMode !== this.state.editMode) {
return { wasEditing: prevState.editMode };
}
return null;
}
// 6. 更新后 - componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('UserProfile updated');
// 适合的操作:
// - 发起新的 API 请求(当 props 变化时)
// - 操作 DOM(基于新的 state/props)
// - 清理和重新设置定时器
if (prevProps.userId !== this.props.userId) {
this.fetchUserData();
}
if (snapshot && snapshot.wasEditing !== this.state.editMode) {
console.log('Edit mode changed from', snapshot.wasEditing, 'to', this.state.editMode);
}
// 如果编辑模式开启,自动聚焦到第一个输入框
if (!prevState.editMode && this.state.editMode) {
this.focusFirstInput();
}
}
// 7. 卸载前 - 组件销毁前执行
componentWillUnmount() {
console.log('UserProfile will unmount');
// 必要的清理操作:
// - 清除定时器
// - 移除事件监听器
// - 取消网络请求
// - 清理订阅
this.cleanup();
}
// 8. 错误处理 - getDerivedStateFromError
static getDerivedStateFromError(error) {
// 更新 state 以显示错误 UI
return { hasError: true, error };
}
// 9. 错误处理 - componentDidCatch
componentDidCatch(error, errorInfo) {
// 记录错误信息
console.error('Error caught in UserProfile:', error, errorInfo);
// 可以发送错误报告到监控服务
this.logErrorToService(error, errorInfo);
}
// 自定义方法
fetchUserData = async () => {
const { userId } = this.props;
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
this.setState({
user: userData,
loading: false,
error: null,
formData: userData
});
} catch (error) {
this.setState({
user: null,
loading: false,
error: error.message
});
}
};
setupWindowResizeListener = () => {
this.handleResize = () => {
// 处理窗口大小变化
console.log('Window resized');
};
window.addEventListener('resize', this.handleResize);
};
focusFirstInput = () => {
if (this.nameInputRef) {
this.nameInputRef.focus();
}
};
cleanup = () => {
if (this.handleResize) {
window.removeEventListener('resize', this.handleResize);
}
// 取消正在进行的请求
if (this.abortController) {
this.abortController.abort();
}
};
handleEdit() {
this.setState({ editMode: true });
}
handleSave() {
const { formData } = this.state;
const { onSave } = this.props;
// 保存数据
this.setState({ loading: true });
fetch(`/api/users/${formData.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(updatedUser => {
this.setState({
user: updatedUser,
formData: updatedUser,
editMode: false,
loading: false
});
if (onSave) {
onSave(updatedUser);
}
})
.catch(error => {
this.setState({
error: error.message,
loading: false
});
});
}
handleCancel() {
const { user } = this.state;
this.setState({
editMode: false,
formData: user || {}
});
}
handleInputChange = (field, value) => {
this.setState(prevState => ({
formData: {
...prevState.formData,
[field]: value
}
}));
};
render() {
const { user, loading, error, editMode, formData } = this.state;
if (loading) {
return <div className="loading">Loading user profile...</div>;
}
if (error) {
return <div className="error">Error: {error}</div>;
}
if (!user) {
return <div className="not-found">User not found</div>;
}
return (
<div className="user-profile">
{editMode ? (
<div className="edit-form">
<h2>Edit Profile</h2>
<div className="form-group">
<label>Name:</label>
<input
ref={input => this.nameInputRef = input}
type="text"
value={formData.name || ''}
onChange={(e) => this.handleInputChange('name', e.target.value)}
/>
</div>
<div className="form-group">
<label>Email:</label>
<input
type="email"
value={formData.email || ''}
onChange={(e) => this.handleInputChange('email', e.target.value)}
/>
</div>
<div className="form-group">
<label>Phone:</label>
<input
type="tel"
value={formData.phone || ''}
onChange={(e) => this.handleInputChange('phone', e.target.value)}
/>
</div>
<div className="form-actions">
<button onClick={this.handleSave} disabled={loading}>
{loading ? 'Saving...' : 'Save'}
</button>
<button onClick={this.handleCancel} disabled={loading}>
Cancel
</button>
</div>
</div>
) : (
<div className="profile-view">
<div className="profile-header">
<img src={user.avatar} alt={user.name} className="avatar" />
<h2>{user.name}</h2>
<button onClick={this.handleEdit} className="edit-btn">
Edit Profile
</button>
</div>
<div className="profile-info">
<div className="info-item">
<label>Email:</label>
<span>{user.email}</span>
</div>
<div className="info-item">
<label>Phone:</label>
<span>{user.phone || 'Not provided'}</span>
</div>
<div className="info-item">
<label>Department:</label>
<span>{user.department}</span>
</div>
<div className="info-item">
<label>Joined:</label>
<span>{new Date(user.joinedAt).toLocaleDateString()}</span>
</div>
</div>
</div>
)}
</div>
);
}
}
// 使用示例
const App = () => {
const [currentUserId, setCurrentUserId] = useState(1);
const handleUserSave = (updatedUser) => {
console.log('User saved:', updatedUser);
};
return (
<div>
<div className="user-selector">
<button onClick={() => setCurrentUserId(1)}>User 1</button>
<button onClick={() => setCurrentUserId(2)}>User 2</button>
<button onClick={() => setCurrentUserId(3)}>User 3</button>
</div>
<UserProfile
userId={currentUserId}
onSave={handleUserSave}
/>
</div>
);
};
3.3.2 Hooks 替代生命周期
Hooks 生命周期对应关系:
import React, { useState, useEffect, useLayoutEffect, useRef, useCallback, useMemo } from 'react';
// useEffect 对应 componentDidMount, componentDidUpdate, componentWillUnmount
const UserProfile = ({ userId, onSave }) => {
// State 对应 constructor 和 this.state
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [editMode, setEditMode] = useState(false);
const [formData, setFormData] = useState({});
// Ref 对应实例变量
const nameInputRef = useRef(null);
const abortControllerRef = useRef(null);
// useEffect 对应 componentDidMount
useEffect(() => {
console.log('UserProfile mounted - equivalent to componentDidMount');
// 组件挂载时执行的操作
fetchUserData();
setupWindowResizeListener();
// useEffect 的返回函数对应 componentWillUnmount
return () => {
console.log('UserProfile unmounting - equivalent to componentWillUnmount');
cleanup();
};
}, []); // 空依赖数组确保只在挂载时执行一次
// useEffect 对应 componentDidUpdate (特定的 props 变化)
useEffect(() => {
console.log('UserId changed - equivalent to componentDidUpdate for userId');
if (userId) {
fetchUserData();
}
}, [userId]); // 依赖 userId,当 userId 变化时执行
// useEffect 对应 componentDidUpdate (特定的 state 变化)
useEffect(() => {
if (editMode && nameInputRef.current) {
console.log('EditMode enabled - focusing input');
nameInputRef.current.focus();
}
}, [editMode]); // 依赖 editMode
// useLayoutEffect 对应 getSnapshotBeforeUpdate + componentDidUpdate 的同步操作
useLayoutEffect(() => {
console.log('Layout effect - synchronous DOM updates');
// 在 DOM 更新后同步执行,类似 getSnapshotBeforeUpdate 的后续操作
});
// useCallback 对应绑定方法,避免重新创建
const handleEdit = useCallback(() => {
setEditMode(true);
}, []);
const handleSave = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${formData.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error('Failed to save user');
}
const updatedUser = await response.json();
setUser(updatedUser);
setFormData(updatedUser);
setEditMode(false);
if (onSave) {
onSave(updatedUser);
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [formData, onSave]);
const handleCancel = useCallback(() => {
setEditMode(false);
setFormData(user || {});
}, [user]);
const handleInputChange = useCallback((field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
}, []);
// useMemo 对应计算属性,类似 getDerivedStateFromProps
const derivedData = useMemo(() => {
if (!user) return null;
return {
fullName: `${user.firstName} ${user.lastName}`,
displayName: user.nickname || `${user.firstName} ${user.lastName}`,
isActive: user.status === 'active',
memberSince: new Date(user.joinedAt).toLocaleDateString()
};
}, [user]);
// 模拟数据获取
const fetchUserData = useCallback(async () => {
if (!userId) return;
setLoading(true);
setError(null);
// 取消之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
try {
const response = await fetch(`/api/users/${userId}`, {
signal: abortControllerRef.current.signal
});
if (!response.ok) {
throw new Error('Failed to fetch user');
}
const userData = await response.json();
setUser(userData);
setFormData(userData);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
setUser(null);
}
} finally {
setLoading(false);
}
}, [userId]);
// 设置事件监听器
const setupWindowResizeListener = useCallback(() => {
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', handleResize);
// 返回清理函数
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// 清理函数
const cleanup = useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
}, []);
// 渲染逻辑
if (loading) {
return <div className="loading">Loading user profile...</div>;
}
if (error) {
return <div className="error">Error: {error}</div>;
}
if (!user) {
return <div className="not-found">User not found</div>;
}
return (
<div className="user-profile">
{editMode ? (
<div className="edit-form">
<h2>Edit Profile</h2>
<div className="form-group">
<label>Name:</label>
<input
ref={nameInputRef}
type="text"
value={formData.name || ''}
onChange={(e) => handleInputChange('name', e.target.value)}
/>
</div>
<div className="form-group">
<label>Email:</label>
<input
type="email"
value={formData.email || ''}
onChange={(e) => handleInputChange('email', e.target.value)}
/>
</div>
<div className="form-group">
<label>Phone:</label>
<input
type="tel"
value={formData.phone || ''}
onChange={(e) => handleInputChange('phone', e.target.value)}
/>
</div>
<div className="form-actions">
<button onClick={handleSave} disabled={loading}>
{loading ? 'Saving...' : 'Save'}
</button>
<button onClick={handleCancel} disabled={loading}>
Cancel
</button>
</div>
</div>
) : (
<div className="profile-view">
<div className="profile-header">
<img src={user.avatar} alt={user.name} className="avatar" />
<h2>{derivedData?.displayName || user.name}</h2>
<button onClick={handleEdit} className="edit-btn">
Edit Profile
</button>
</div>
<div className="profile-info">
<div className="info-item">
<label>Email:</label>
<span>{user.email}</span>
</div>
<div className="info-item">
<label>Phone:</label>
<span>{user.phone || 'Not provided'}</span>
</div>
<div className="info-item">
<label>Status:</label>
<span className={derivedData?.isActive ? 'active' : 'inactive'}>
{user.status}
</span>
</div>
<div className="info-item">
<label>Member Since:</label>
<span>{derivedData?.memberSince}</span>
</div>
</div>
</div>
)}
</div>
);
};
// 错误边界组件 (类组件专用功能)
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// 对应 getDerivedStateFromError
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 对应 componentDidCatch
this.setState({
error: error,
errorInfo: errorInfo
});
// 记录错误到监控服务
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
// 使用示例
const App = () => {
const [currentUserId, setCurrentUserId] = useState(1);
const handleUserSave = useCallback((updatedUser) => {
console.log('User saved:', updatedUser);
}, []);
return (
<ErrorBoundary>
<div>
<div className="user-selector">
<button onClick={() => setCurrentUserId(1)}>User 1</button>
<button onClick={() => setCurrentUserId(2)}>User 2</button>
<button onClick={() => setCurrentUserId(999)}>Invalid User</button>
</div>
<UserProfile
userId={currentUserId}
onSave={handleUserSave}
/>
</div>
</ErrorBoundary>
);
};
通过本章的组件系统详解,你已经全面掌握了 React 组件的设计原则、通信方式、生命周期管理以及 Hooks 的使用技巧。这些知识将帮助你构建结构清晰、可维护性强的 React 应用。

浙公网安备 33010602011771号