一、组件化思想与核心概念
1.1 什么是组件化?
组件化是将用户界面拆分为独立、可复用、可组合的代码单元的开发模式。React 组件化基于以下几个核心理念:
- 单一职责原则:每个组件只关注一个特定功能
- 封装性:组件内部状态和逻辑对外隐藏,通过 props 接口通信
- 可组合性:小组件可以组合成更复杂的组件
- 可复用性:一次编写,多处使用
1.2 React 组件化优势
javascript
// 传统方式 vs 组件化方式
// 传统:代码重复,难以维护
function renderUserProfile(user) {
return `
<div class="profile">
<img src="${user.avatar}" class="avatar"/>
<h2>${user.name}</h2>
<p>${user.bio}</p>
</div>
`;
}
function renderUserCard(user) {
return `
<div class="card">
<img src="${user.avatar}" class="avatar"/>
<h2>${user.name}</h2>
</div>
`;
}
// React组件化:复用Avatar组件
const Avatar = ({ src, size }) => (
<img src={src} className={`avatar avatar-${size}`} />
);
const UserProfile = ({ user }) => (
<div className="profile">
<Avatar src={user.avatar} size="large" />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
const UserCard = ({ user }) => (
<div className="card">
<Avatar src={user.avatar} size="medium" />
<h2>{user.name}</h2>
</div>
);
二、组件类型与创建方式
2.1 函数组件 (Function Components)
jsx
// 基础函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 箭头函数组件
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
// 隐式返回
const Welcome = props => <h1>Hello, {props.name}</h1>;
2.2 类组件 (Class Components)
jsx
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('Component mounted');
}
handleClick = () => {
this.setState(prevState => ({
count: prevState.count + 1
}));
};
render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
);
}
}
2.3 组件组合模式
jsx
// 容器组件
const UserDashboard = () => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser().then(userData => {
setUser(userData);
setLoading(false);
});
}, []);
if (loading) return <LoadingSpinner />;
if (!user) return <ErrorPage />;
return (
<div className="dashboard">
<UserHeader user={user} />
<UserStats stats={user.stats} />
<RecentActivity activities={user.activities} />
</div>
);
};
// 展示组件
const UserHeader = ({ user }) => (
<header>
<Avatar src={user.avatar} size="large" />
<h1>{user.name}</h1>
<p>{user.title}</p>
</header>
);
三、Props:组件间通信
3.1 Props 基础使用
jsx
// 传递 props
const App = () => (
<UserProfile
name="John Doe"
age={30}
isOnline={true}
onLogin={() => console.log('Logged in')}
/>
);
// 接收 props
const UserProfile = ({ name, age, isOnline, onLogin }) => (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Status: {isOnline ? 'Online' : 'Offline'}</p>
<button onClick={onLogin}>Login</button>
</div>
);
3.2 Props 验证 (PropTypes)
jsx
import PropTypes from 'prop-types';
const UserCard = ({ user, onEdit, showActions }) => (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
{showActions && <button onClick={() => onEdit(user.id)}>Edit</button>}
</div>
);
UserCard.propTypes = {
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired
}).isRequired,
onEdit: PropTypes.func,
showActions: PropTypes.bool
};
UserCard.defaultProps = {
showActions: true,
onEdit: () => {}
};
3.3 Children Props
jsx
// 容器组件
const Card = ({ title, children, footer }) => (
<div className="card">
{title && <div className="card-header">{title}</div>}
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
// 使用
const UserProfile = () => (
<Card
title="User Information"
footer={<button>Save Changes</button>}
>
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
<p>Location: New York</p>
</Card>
);
四、State:组件内部状态管理
4.1 useState Hook
jsx
import { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
const increment = () => setCount(prev => prev + 1);
const updateName = name => setUser(prev => ({ ...prev, name }));
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<input
value={user.name}
onChange={e => updateName(e.target.value)}
placeholder="Enter name"
/>
</div>
);
};
4.2 状态提升 (Lifting State Up)
jsx
// 父组件管理状态
const ParentComponent = () => {
const [sharedData, setSharedData] = useState('');
return (
<div>
<ChildA data={sharedData} onDataChange={setSharedData} />
<ChildB data={sharedData} />
<ChildC data={sharedData} />
</div>
);
};
// 子组件A - 修改数据
const ChildA = ({ data, onDataChange }) => (
<input
value={data}
onChange={e => onDataChange(e.target.value)}
/>
);
// 子组件B - 显示数据
const ChildB = ({ data }) => <p>Data: {data}</p>;
// 子组件C - 使用数据
const ChildC = ({ data }) => <div>Length: {data.length}</div>;
4.3 使用Reducer管理复杂状态
jsx
import { useReducer } from 'react';
const initialState = {
loading: false,
data: null,
error: null
};
function apiReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { loading: false, data: action.payload, error: null };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
const ApiComponent = () => {
const [state, dispatch] = useReducer(apiReducer, initialState);
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
};
return (
<div>
<button onClick={fetchData} disabled={state.loading}>
{state.loading ? 'Loading...' : 'Fetch Data'}
</button>
{state.error && <p>Error: {state.error}</p>}
{state.data && <pre>{JSON.stringify(state.data, null, 2)}</pre>}
</div>
);
};
五、生命周期与副作用管理
5.1 useEffect Hook
jsx
import { useState, useEffect } from 'react';
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 组件挂载和userId变化时执行
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
setLoading(true);
try {
const userData = await fetchUserById(userId);
if (isMounted) {
setUser(userData);
}
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
// 清理函数
return () => {
isMounted = false;
};
}, [userId]); // 依赖数组
// 监听用户数据变化
useEffect(() => {
if (user) {
document.title = `${user.name}'s Profile`;
// 跟踪用户查看事件
trackAnalytics('profile_view', { userId: user.id });
}
}, [user]);
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
};
5.2 自定义Hook封装逻辑
jsx
// 自定义Hook
const useApi = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
// 使用自定义Hook
const UserList = () => {
const { data: users, loading, error } = useApi('/api/users');
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
六、组件通信模式
6.1 父子组件通信
jsx
// 父组件
const Parent = () => {
const [items, setItems] = useState([]);
const addItem = (newItem) => {
setItems(prev => [...prev, newItem]);
};
const removeItem = (itemId) => {
setItems(prev => prev.filter(item => item.id !== itemId));
};
return (
<div>
<ItemForm onAddItem={addItem} />
<ItemList items={items} onRemoveItem={removeItem} />
</div>
);
};
// 子组件 - 表单
const ItemForm = ({ onAddItem }) => {
const [input, setInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim()) {
onAddItem({
id: Date.now(),
text: input.trim()
});
setInput('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Add new item"
/>
<button type="submit">Add</button>
</form>
);
};
// 子组件 - 列表
const ItemList = ({ items, onRemoveItem }) => (
<ul>
{items.map(item => (
<li key={item.id}>
{item.text}
<button onClick={() => onRemoveItem(item.id)}>Remove</button>
</li>
))}
</ul>
);
6.2 兄弟组件通信(通过父组件)
jsx
const Dashboard = () => {
const [selectedTab, setSelectedTab] = useState('overview');
return (
<div className="dashboard">
<TabNavigation selectedTab={selectedTab} onTabChange={setSelectedTab} />
<TabContent selectedTab={selectedTab} />
</div>
);
};
const TabNavigation = ({ selectedTab, onTabChange }) => (
<nav>
{['overview', 'analytics', 'settings'].map(tab => (
<button
key={tab}
className={selectedTab === tab ? 'active' : ''}
onClick={() => onTabChange(tab)}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</nav>
);
const TabContent = ({ selectedTab }) => {
switch (selectedTab) {
case 'overview':
return <OverviewTab />;
case 'analytics':
return <AnalyticsTab />;
case 'settings':
return <SettingsTab />;
default:
return null;
}
};
6.3 Context API 跨组件通信
jsx
import { createContext, useContext, useState } from 'react';
// 创建Context
const ThemeContext = createContext();
// Provider组件
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const value = {
theme,
toggleTheme,
isDark: theme === 'dark'
};
return (
<ThemeContext.Provider value={value}>
<div className={`theme-${theme}`}>
{children}
</div>
</ThemeContext.Provider>
);
};
// 自定义Hook使用Context
const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
// 使用Context的组件
const ThemeToggle = () => {
const { toggleTheme, isDark } = useTheme();
return (
<button onClick={toggleTheme}>
Switch to {isDark ? 'Light' : 'Dark'} Mode
</button>
);
};
const App = () => (
<ThemeProvider>
<Header />
<MainContent />
<Footer />
</ThemeProvider>
);
const Header = () => {
const { theme } = useTheme();
return <header className={`header-${theme}`}>Header</header>;
};
七、高级组件模式
7.1 高阶组件 (HOC)
jsx
// 高阶组件:添加加载状态
const withLoading = (WrappedComponent) => {
return function WithLoadingComponent(props) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// 假设props有fetchData方法
const result = await props.fetchData();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, [props.fetchData]);
if (loading) return <div>Loading...</div>;
return <WrappedComponent {...props} data={data} />;
};
};
// 使用高阶组件
const UserList = ({ data }) => (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
const UserListWithLoading = withLoading(UserList);
// 使用
const App = () => (
<UserListWithLoading
fetchData={() => fetch('/api/users').then(res => res.json())}
/>
);
7.2 Render Props 模式
jsx
// Render Props 组件
class DataFetcher extends React.Component {
state = {
data: null,
loading: true,
error: null
};
async componentDidMount() {
try {
const data = await this.props.fetchData();
this.setState({ data, loading: false });
} catch (error) {
this.setState({ error: error.message, loading: false });
}
}
render() {
return this.props.children(this.state);
}
}
// 使用
const UserProfile = () => (
<DataFetcher
fetchData={() => fetch('/api/user/1').then(res => res.json())}
>
{({ data, loading, error }) => {
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}}
</DataFetcher>
);
7.3 复合组件模式
jsx
// 复合组件:Tabs
const Tabs = ({ children }) => {
const [activeTab, setActiveTab] = useState(0);
return (
<div className="tabs">
<div className="tabs-header">
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isActive: index === activeTab,
onSelect: () => setActiveTab(index)
})
)}
</div>
<div className="tabs-content">
{React.Children.toArray(children)[activeTab].props.children}
</div>
</div>
);
};
const Tab = ({ label, isActive, onSelect, children }) => (
<button
className={`tab ${isActive ? 'active' : ''}`}
onClick={onSelect}
>
{label}
</button>
);
// 使用复合组件
const App = () => (
<Tabs>
<Tab label="Overview">
<h2>Overview Content</h2>
<p>This is the overview tab content.</p>
</Tab>
<Tab label="Details">
<h2>Details Content</h2>
<p>This is the details tab content.</p>
</Tab>
<Tab label="Settings">
<h2>Settings Content</h2>
<p>This is the settings tab content.</p>
</Tab>
</Tabs>
);
八、性能优化
8.1 React.memo
jsx
// 使用React.memo避免不必要的重新渲染
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('ExpensiveComponent rendered');
return (
<div>
<h2>Expensive Component</h2>
<p>Data: {JSON.stringify(data)}</p>
<button onClick={() => onUpdate(Date.now())}>Update</button>
</div>
);
});
// 自定义比较函数
const areEqual = (prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id;
};
const UserCard = React.memo(({ user }) => (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
), areEqual);
8.2 useCallback 和 useMemo
jsx
const UserDashboard = ({ userId }) => {
const [user, setUser] = useState(null);
const [preferences, setPreferences] = useState({});
// 使用useCallback记忆函数
const fetchUser = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
return await response.json();
}, [userId]);
// 使用useMemo记忆计算结果
const userStats = useMemo(() => {
if (!user) return null;
return {
totalPosts: user.posts.length,
averageLikes: user.posts.reduce((sum, post) => sum + post.likes, 0) / user.posts.length,
engagementRate: calculateEngagementRate(user)
};
}, [user]);
useEffect(() => {
fetchUser().then(setUser);
}, [fetchUser]);
return (
<div>
{user && (
<>
<UserProfile user={user} />
<UserStats stats={userStats} />
<UserActions onUpdate={setPreferences} />
</>
)}
</div>
);
};
8.3 代码分割与懒加载
jsx
import { lazy, Suspense } from 'react';
// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const UserSettings = lazy(() => import('./UserSettings'));
const App = () => {
const [showHeavy, setShowHeavy] = useState(false);
return (
<div>
<button onClick={() => setShowHeavy(true)}>
Load Heavy Component
</button>
<Suspense fallback={<div>Loading...</div>}>
{showHeavy && <HeavyComponent />}
</Suspense>
<Suspense fallback={<div>Loading Settings...</div>}>
<UserSettings />
</Suspense>
</div>
);
};
九、最佳实践与设计原则
9.1 组件设计原则
jsx
// 1. 单一职责原则
// 不好:组件做太多事情
const UserDashboard = () => {
// 用户数据获取、UI渲染、业务逻辑全部在一起
};
// 好:拆分为多个单一职责组件
const UserDashboard = () => (
<div>
<UserHeader />
<UserStats />
<RecentActivity />
</div>
);
// 2. 受控组件 vs 非受控组件
// 受控组件:数据由React state管理
const ControlledInput = ({ value, onChange }) => (
<input value={value} onChange={onChange} />
);
// 非受控组件:数据由DOM管理
const UncontrolledInput = () => {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</div>
);
};
// 3. 合理的组件结构
src/
components/
common/ # 通用组件
Button/
Button.jsx
Button.css
index.js
Modal/
Modal.jsx
Modal.css
index.js
features/ # 功能组件
auth/
LoginForm/
LoginForm.jsx
LoginForm.css
index.js
dashboard/
UserCard/
UserCard.jsx
UserCard.css
index.js
9.2 错误边界
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
// 可以在这里上报错误到监控系统
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-boundary">
<h2>Something went wrong</h2>
<details>
{this.state.error.toString()}
</details>
</div>
);
}
return this.props.children;
}
}
// 使用错误边界
const App = () => (
<ErrorBoundary fallback={<div>Custom error message</div>}>
<UserDashboard />
<SettingsPanel />
</ErrorBoundary>
);
十、测试策略
10.1 组件测试
jsx
// UserCard.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import UserCard from './UserCard';
describe('UserCard', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com'
};
test('renders user information', () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
test('calls onEdit when edit button is clicked', () => {
const handleEdit = jest.fn();
render(<UserCard user={mockUser} onEdit={handleEdit} />);
fireEvent.click(screen.getByText('Edit'));
expect(handleEdit).toHaveBeenCalledWith(1);
});
test('does not show edit button when showActions is false', () => {
render(<UserCard user={mockUser} showActions={false} />);
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
});
});
10.2 Hook 测试
jsx
// useApi.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import { useApi } from './useApi';
describe('useApi', () => {
test('fetches data successfully', async () => {
const mockData = { id: 1, name: 'Test' };
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockData)
})
);
const { result, waitForNextUpdate } = renderHook(() =>
useApi('/api/test')
);
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeNull();
});
});
总结
React 组件化是现代前端开发的基石,它通过以下核心概念提供了强大的开发体验:
- 组件类型:函数组件(推荐)和类组件
- 数据流:Props 向下传递,事件向上传递
- 状态管理:useState、useReducer、Context API
- 生命周期:useEffect 处理副作用
- 性能优化:React.memo、useCallback、useMemo
- 高级模式:HOC、Render Props、复合组件
- 最佳实践:单一职责、错误边界、测试策略