React组件完全指南
1. 组件基础概念
什么是React组件?
React组件是构建用户界面的基本单元,它是一个可复用的代码片段,接收输入(props)并返回描述界面应该如何显示的React元素。
组件的核心功能
- 封装性:将相关的逻辑和UI封装在一起
- 可复用性:可在不同地方重复使用
- 组合性:可以组合多个组件构建复杂界面
- 单向数据流:数据从父组件向子组件流动
2. 组件类型
2.1 函数组件(推荐)
// 基础函数组件
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 类组件(传统方式)
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基础用法
// 父组件传递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默认值和类型检查
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传递技巧
// 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
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 状态更新最佳实践
// ❌ 错误:直接修改状态
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 - 副作用处理
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 - 跨组件状态共享
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 - 性能优化
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 容器组件和展示组件
// 容器组件 - 处理逻辑和状态
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)
// 高阶组件 - 添加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
// 自定义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 - 防止不必要的重渲染
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 列表优化
// ✅ 正确:使用稳定的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 代码分割和懒加载
import { lazy, Suspense } from 'react';// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));function App() {return (<div><Suspense fallback={<div>加载中...</div>}><HeavyComponent /></Suspense></div>);
}
8. 最佳实践
8.1 组件命名和结构
// ✅ 好的命名
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.jsxUserProfileCard.cssUserProfileCard.test.js
8.2 事件处理
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 错误处理
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 (<ErrorBoundaryFallbackComponent={ErrorFallback}onError={(error, errorInfo) => {console.error('错误记录:', error, errorInfo);}}onReset={() => {// 重置状态}}><MyComponent /></ErrorBoundary>);
}
9. 常见注意事项
9.1 避免常见错误
// ❌ 错误:在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 内存泄漏防护
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 条件渲染技巧
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开发者工具
// 为组件添加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调试
import { useDebugValue } from 'react';function useCustomHook(value) {const [state, setState] = useState(value);// 在开发者工具中显示调试信息useDebugValue(state, state => `当前值: ${state}`);return [state, setState];
}
React组件是构建现代前端应用的核心,掌握这些概念、模式和最佳实践将帮助你编写更高质量、更可维护的React应用。记住要保持组件简单、可预测,并始终考虑性能和用户体验。