1. 组件基础概念
什么是React组件?
React组件是构建用户界面的基本单元,它是一个可复用的代码片段,接收输入(props)并返回描述界面应该如何显示的React元素。
组件的核心功能
- 封装性:将相关的逻辑和UI封装在一起
- 可复用性:可在不同地方重复使用
- 组合性:可以组合多个组件构建复杂界面
- 单向数据流:数据从父组件向子组件流动
2. 组件类型
2.1 函数组件(推荐)
jsx
// 基础函数组件
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 箭头函数组件
const Welcome = (props) => {
return <h1>Hello, {props.name}!</h1>;
}
// 带Hooks的函数组件
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
2.2 类组件(传统方式)
jsx
import React, { Component } from 'react';
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Count: ${this.state.count}`;
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.handleClick}>
Click me
</button>
</div>
);
}
}
3. Props(属性)
3.1 Props基础用法
jsx
// 父组件传递props
function App() {
return (
<div>
<UserCard
name="张三"
age={25}
email="zhangsan@example.com"
isActive={true}
/>
</div>
);
}
// 子组件接收props
function UserCard({ name, age, email, isActive }) {
return (
<div className={`user-card ${isActive ? 'active' : ''}`}>
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
);
}
3.2 Props默认值和类型检查
jsx
import PropTypes from 'prop-types';
function Button({ children, variant, size, onClick }) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
onClick={onClick}
>
{children}
</button>
);
}
// 默认props
Button.defaultProps = {
variant: 'primary',
size: 'medium'
};
// PropTypes类型检查
Button.propTypes = {
children: PropTypes.node.isRequired,
variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),
size: PropTypes.oneOf(['small', 'medium', 'large']),
onClick: PropTypes.func
};
3.3 Props传递技巧
jsx
// 1. 展开运算符传递props
const userProps = {
name: "李四",
age: 30,
email: "lisi@example.com"
};
<UserCard {...userProps} />
// 2. 透传props
function CardWrapper({ children, ...otherProps }) {
return (
<div className="card-wrapper">
<Card {...otherProps}>
{children}
</Card>
</div>
);
}
// 3. render props模式
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// 使用render props
<DataFetcher
url="/api/users"
render={({ data, loading }) => (
loading ? <div>加载中...</div> : <UserList users={data} />
)}
/>
4. State(状态)
4.1 useState Hook
jsx
import { useState } from 'react';
function FormExample() {
// 基础状态
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// 对象状态
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// 数组状态
const [items, setItems] = useState([]);
// 更新对象状态
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
// 更新数组状态
const addItem = (item) => {
setItems(prevItems => [...prevItems, item]);
};
const removeItem = (index) => {
setItems(prevItems => prevItems.filter((_, i) => i !== index));
};
return (
<form>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="姓名"
/>
<input
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="邮箱"
/>
</form>
);
}
4.2 状态更新最佳实践
jsx
// ❌ 错误:直接修改状态
const [user, setUser] = useState({ name: 'John', age: 25 });
user.age = 26; // 不要这样做
setUser(user);
// ✅ 正确:创建新对象
setUser(prevUser => ({ ...prevUser, age: 26 }));
// ❌ 错误:依赖当前状态的更新
const [count, setCount] = useState(0);
setCount(count + 1);
// ✅ 正确:使用函数式更新
setCount(prevCount => prevCount + 1);
5. 常用Hooks
5.1 useEffect - 副作用处理
jsx
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 组件挂载和userId变化时执行
useEffect(() => {
let cancelled = false;
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (!cancelled) {
setUser(userData);
setLoading(false);
}
} catch (error) {
if (!cancelled) {
setLoading(false);
}
}
};
fetchUser();
// 清理函数,防止内存泄漏
return () => {
cancelled = true;
};
}, [userId]);
// 只在挂载时执行一次
useEffect(() => {
document.title = '用户资料';
return () => {
document.title = '应用名称'; // 清理
};
}, []);
if (loading) return <div>加载中...</div>;
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>
);
}
5.2 useContext - 跨组件状态共享
jsx
import { createContext, useContext, useState } from 'react';
// 创建Context
const ThemeContext = createContext();
// Provider组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 使用Context的组件
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
className={`btn btn-${theme}`}
onClick={toggleTheme}
>
当前主题: {theme}
</button>
);
}
5.3 useMemo 和 useCallback - 性能优化
jsx
import { useState, useMemo, useCallback } from 'react';
function ExpensiveComponent({ items, filter }) {
const [count, setCount] = useState(0);
// 缓存昂贵的计算
const filteredItems = useMemo(() => {
console.log('过滤计算执行');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
// 缓存回调函数
const handleItemClick = useCallback((item) => {
console.log('点击了:', item.name);
}, []);
// 缓存派生状态
const itemCount = useMemo(() => filteredItems.length, [filteredItems]);
return (
<div>
<p>计数: {count}</p>
<p>过滤后项目数: {itemCount}</p>
<button onClick={() => setCount(c => c + 1)}>
增加计数
</button>
{filteredItems.map(item => (
<ItemComponent
key={item.id}
item={item}
onClick={handleItemClick}
/>
))}
</div>
);
}
6. 组件设计模式
6.1 容器组件和展示组件
jsx
// 容器组件 - 处理逻辑和状态
function UserListContainer() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setLoading(false);
});
}, []);
const handleDeleteUser = (userId) => {
setUsers(users.filter(user => user.id !== userId));
};
return (
<UserListPresentation
users={users}
loading={loading}
onDeleteUser={handleDeleteUser}
/>
);
}
// 展示组件 - 只负责渲染
function UserListPresentation({ users, loading, onDeleteUser }) {
if (loading) {
return <div>加载中...</div>;
}
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onDelete={() => onDeleteUser(user.id)}
/>
))}
</div>
);
}
6.2 高阶组件 (HOC)
jsx
// 高阶组件 - 添加loading功能
function withLoading(WrappedComponent) {
return function WithLoadingComponent(props) {
if (props.loading) {
return <div>加载中...</div>;
}
return <WrappedComponent {...props} />;
};
}
// 使用HOC
const UserListWithLoading = withLoading(UserList);
// 使用
<UserListWithLoading users={users} loading={isLoading} />
6.3 自定义Hook
jsx
// 自定义Hook - 数据获取
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
if (!cancelled) {
setData(result);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
// 使用自定义Hook
function UserProfile({ userId }) {
const { data: user, loading, error } = useApi(`/api/users/${userId}`);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
7. 性能优化
7.1 React.memo - 防止不必要的重渲染
jsx
import { memo } from 'react';
// 使用memo包装组件
const UserCard = memo(function UserCard({ user, onEdit }) {
console.log('UserCard渲染');
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onEdit(user.id)}>编辑</button>
</div>
);
});
// 自定义比较函数
const UserCardWithCustomCompare = memo(UserCard, (prevProps, nextProps) => {
return prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name;
});
7.2 列表优化
jsx
// ✅ 正确:使用稳定的key
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
// ❌ 错误:使用索引作为key(在动态列表中)
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}
</ul>
);
}
7.3 代码分割和懒加载
jsx
import { lazy, Suspense } from 'react';
// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
8. 最佳实践
8.1 组件命名和结构
jsx
// ✅ 好的命名
function UserProfileCard({ user, isEditable, onEdit }) {
return (
<div className="user-profile-card">
<UserAvatar src={user.avatar} alt={user.name} />
<UserInfo user={user} />
{isEditable && (
<EditButton onClick={() => onEdit(user.id)} />
)}
</div>
);
}
// ✅ 清晰的文件结构
components/
UserProfileCard/
index.js // 导出组件
UserProfileCard.jsx
UserProfileCard.css
UserProfileCard.test.js
8.2 事件处理
jsx
function TodoItem({ todo, onToggle, onDelete }) {
// ✅ 使用useCallback优化
const handleToggle = useCallback(() => {
onToggle(todo.id);
}, [todo.id, onToggle]);
const handleDelete = useCallback(() => {
onDelete(todo.id);
}, [todo.id, onDelete]);
// ✅ 或者在JSX中直接传递参数
return (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</div>
);
}
8.3 错误处理
jsx
import { ErrorBoundary } from 'react-error-boundary';
// 错误边界组件
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<h2>出错了:</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}
// 使用错误边界
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, errorInfo) => {
console.error('错误记录:', error, errorInfo);
}}
onReset={() => {
// 重置状态
}}
>
<MyComponent />
</ErrorBoundary>
);
}
9. 常见注意事项
9.1 避免常见错误
jsx
// ❌ 错误:在render中定义函数
function BadComponent() {
return (
<button onClick={() => {
// 每次渲染都创建新函数
console.log('clicked');
}}>
点击
</button>
);
}
// ✅ 正确:使用useCallback或提取到组件外部
function GoodComponent() {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <button onClick={handleClick}>点击</button>;
}
// ❌ 错误:在useEffect中缺少依赖
function BadEffect({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // 缺少userId依赖
return <div>{user?.name}</div>;
}
// ✅ 正确:包含所有依赖
function GoodEffect({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // 包含userId依赖
return <div>{user?.name}</div>;
}
9.2 内存泄漏防护
jsx
function ComponentWithCleanup() {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
// 定时器清理
const timer = setInterval(() => {
if (!cancelled) {
// 更新数据
}
}, 1000);
// 事件监听器清理
const handleResize = () => {
if (!cancelled) {
// 处理resize
}
};
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
cancelled = true;
clearInterval(timer);
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>{data}</div>;
}
9.3 条件渲染技巧
jsx
function ConditionalRendering({ user, loading, error }) {
// ✅ 使用早期返回
if (loading) {
return <LoadingSpinner />;
}
if (error) {
return <ErrorMessage error={error} />;
}
if (!user) {
return <EmptyState message="用户不存在" />;
}
return (
<div>
{/* ✅ 使用逻辑与进行条件渲染 */}
{user.isVip && <VipBadge />}
{/* ✅ 使用三元运算符 */}
<div className={user.isActive ? 'active' : 'inactive'}>
{user.name}
</div>
{/* ❌ 避免使用 && 运算符和falsy值 */}
{user.posts.length && <PostList posts={user.posts} />}
{/* ✅ 正确的写法 */}
{user.posts.length > 0 && <PostList posts={user.posts} />}
</div>
);
}
10. 调试技巧
10.1 React开发者工具
jsx
// 为组件添加displayName便于调试
const UserCard = memo(function UserCard(props) {
return <div>{/* ... */}</div>;
});
UserCard.displayName = 'UserCard';
// 使用React.StrictMode检测问题
function App() {
return (
<React.StrictMode>
<MyApp />
</React.StrictMode>
);
}
10.2 自定义Hook调试
jsx
import { useDebugValue } from 'react';
function useCustomHook(value) {
const [state, setState] = useState(value);
// 在开发者工具中显示调试信息
useDebugValue(state, state => `当前值: ${state}`);
return [state, setState];
}
React组件是构建现代前端应用的核心,掌握这些概念、模式和最佳实践将帮助你编写更高质量、更可维护的React应用。记住要保持组件简单、可预测,并始终考虑性能和用户体验。