1) 保持组件小而专注
一个组件理想情况下应该做好一件事。大型的"上帝组件"难以测试和维护。将大组件拆分为更小、可重用的组件。
不良实践
// 一个做太多事情的组件
function UserDashboard() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [filters, setFilters] = useState({});
const [pagination, setPagination] = useState({ page: 1, limit: 10 });
// 用户管理逻辑
const fetchUsers = async () => { /* ... */ };
const createUser = async () => { /* ... */ };
const updateUser = async () => { /* ... */ };
const deleteUser = async () => { /* ... */ };
// 过滤和分页逻辑
const applyFilters = () => { /* ... */ };
const handlePageChange = () => { /* ... */ };
// 复杂的渲染逻辑
return (
<div>
{/* 用户列表 */}
{/* 用户表单 */}
{/* 过滤器 */}
{/* 分页 */}
{/* 统计信息 */}
</div>
);
}
清洁实践
// 拆分为多个专注的组件
function UserDashboard() {
return (
<div>
<UserFilters />
<UserList />
<UserPagination />
<UserStats />
</div>
);
}
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const fetchUsers = async () => { /* ... */ };
return (
<div>
{loading ? <LoadingSpinner /> : <UserTable users={users} />}
</div>
);
}
2) 使用函数组件和Hooks
对于大多数用例,类组件已经过时。拥抱带有hooks的函数组件来处理状态、副作用等——它们更简单、更简洁。
类组件(过时)
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState(prevState => ({ count: prevState.count + 1 }));
};
render() {
return (
<button onClick={this.increment}>
Count: {this.state.count}
</button>
);
}
}
函数组件和Hooks
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
3) 解构Props和State
避免到处使用props.someValue。相反,解构props和state以获得更干净、更可读的代码:
不良实践
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
function UserProfile(props) {
return (
<div>
<h2>{props.user.name}</h2>
<p>{props.user.email}</p>
<span>{props.user.role}</span>
</div>
);
}
清洁实践
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
function UserProfile({ user: { name, email, role } }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<span>{role}</span>
</div>
);
}
4) 保持JSX可读
长而深度嵌套的JSX难以阅读。用辅助函数或子组件来分解它。
不良实践
function ProductCard({ product }) {
return (
<div className="product-card">
<div className="product-image">
<img src={product.image} alt={product.name} />
{product.isNew && <span className="badge">New</span>}
{product.discount > 0 && (
<span className="discount-badge">
-{product.discount}%
</span>
)}
</div>
<div className="product-info">
<h3>{product.name}</h3>
<p>{product.description}</p>
<div className="price-section">
<span className="current-price">${product.price}</span>
{product.originalPrice > product.price && (
<span className="original-price">
${product.originalPrice}
</span>
)}
</div>
<div className="actions">
<button className="add-to-cart">Add to Cart</button>
<button className="wishlist"></button>
</div>
</div>
</div>
);
}
清洁实践
function ProductCard({ product }) {
return (
<div className="product-card">
<ProductImage product={product} />
<ProductInfo product={product} />
<ProductActions product={product} />
</div>
);
}
function ProductImage({ product }) {
return (
<div className="product-image">
<img src={product.image} alt={product.name} />
{product.isNew && <Badge type="new" />}
{product.discount > 0 && (
<Badge type="discount" value={product.discount} />
)}
</div>
);
}
function ProductInfo({ product }) {
return (
<div className="product-info">
<h3>{product.name}</h3>
<p>{product.description}</p>
<PriceDisplay product={product} />
</div>
);
}
function ProductActions({ product }) {
return (
<div className="actions">
<button className="add-to-cart">Add to Cart</button>
<button className="wishlist"></button>
</div>
);
}
5) 使用PropTypes或TypeScript
始终使用PropTypes验证组件props,或者更好的是,迁移到TypeScript以获得更安全、自文档化的代码。
使用PropTypes
import PropTypes from 'prop-types';
function UserCard({ user, onEdit, onDelete }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<div className="actions">
<button onClick={() => onEdit(user.id)}>Edit</button>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
</div>
);
}
UserCard.propTypes = {
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
}).isRequired,
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};
使用TypeScript
interface User {
id: number;
name: string;
email: string;
}
interface UserCardProps {
user: User;
onEdit: (id: number) => void;
onDelete: (id: number) => void;
}
function UserCard({ user, onEdit, onDelete }: UserCardProps) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<div className="actions">
<button onClick={() => onEdit(user.id)}>Edit</button>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
</div>
);
}
6) 利用React DevTools
在浏览器中使用React Developer Tools来检查组件树、props和state——它将为您节省数小时调试棘手问题的时间。
使用DevTools的技巧
- 组件树检查:查看组件层次结构
- Props和State检查:实时查看数据流
- 性能分析:识别不必要的重新渲染
- Profiler:分析组件渲染时间
7) 记忆化昂贵操作
使用React.memo、useMemo和useCallback避免不必要的重新渲染,特别是对于大型列表或密集计算。
使用React.memo
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
// 昂贵的计算
const processedData = data.map(item => ({
...item,
processed: heavyComputation(item)
}));
return (
<div>
{processedData.map(item => (
<div key={item.id}>{item.processed}</div>
))}
</div>
);
});
使用useMemo
function ProductList({ products, filters }) {
const filteredProducts = useMemo(() => {
return products.filter(product => {
return filters.category === 'all' ||
product.category === filters.category;
});
}, [products, filters.category]);
const sortedProducts = useMemo(() => {
return [...filteredProducts].sort((a, b) => {
if (filters.sortBy === 'price') {
return a.price - b.price;
}
return a.name.localeCompare(b.name);
});
}, [filteredProducts, filters.sortBy]);
return (
<div>
{sortedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
使用useCallback
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // 空依赖数组,函数永远不会改变
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<ChildComponent onAction={handleClick} />
</div>
);
}
const ChildComponent = React.memo(function ChildComponent({ onAction }) {
return <button onClick={onAction}>Child Action</button>;
});
8) 清理副作用
使用useEffect时,始终清理订阅、定时器或事件监听器以防止内存泄漏。
正确的useEffect使用
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const id = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(id); // 清理!
}, []);
return <div>Current time: {time.toLocaleTimeString()}</div>;
}
事件监听器清理
function WindowResizeHandler() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<div>
Window size: {windowSize.width} x {windowSize.height}
</div>
);
}
API订阅清理
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (isMounted) {
setUser(userData);
setLoading(false);
}
} catch (error) {
if (isMounted) {
console.error('Failed to fetch user:', error);
setLoading(false);
}
}
};
fetchUser();
return () => {
isMounted = false; // 防止在组件卸载后设置状态
};
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
9) 尽可能保持状态本地
不要不必要地提升状态。本地状态减少复杂性和重新渲染,使您的组件更快、更容易维护。
不必要的状态提升
// 父组件管理所有状态
function ParentComponent() {
const [child1Data, setChild1Data] = useState('');
const [child2Data, setChild2Data] = useState('');
const [child3Data, setChild3Data] = useState('');
return (
<div>
<Child1 data={child1Data} setData={setChild1Data} />
<Child2 data={child2Data} setData={setChild2Data} />
<Child3 data={child3Data} setData={setChild3Data} />
</div>
);
}
本地状态管理
// 每个组件管理自己的状态
function ParentComponent() {
return (
<div>
<Child1 />
<Child2 />
<Child3 />
</div>
);
}
function Child1() {
const [data, setData] = useState('');
// 只在需要时使用本地状态
return <input value={data} onChange={e => setData(e.target.value)} />;
}
何时提升状态
// 当多个组件需要共享状态时
function TodoApp() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
return (
<div>
<TodoForm onAdd={addTodo} />
<TodoList todos={todos} onToggle={toggleTodo} />
<TodoStats todos={todos} />
</div>
);
}
10) 使用有意义的命名
始终为组件、props和hooks使用清晰、描述性的名称。像Button、handleClick或isLoading这样的名称使您的代码自解释,更容易被他人理解。
不良命名
function Comp({ d, f }) {
const [l, setL] = useState(false);
const hc = () => {
setL(true);
f(d);
};
return <button onClick={hc}>Click</button>;
}
有意义的命名
function UserButton({ user, onUserClick }) {
const [isLoading, setIsLoading] = useState(false);
const handleClick = () => {
setIsLoading(true);
onUserClick(user);
};
return (
<button
onClick={handleClick}
disabled={isLoading}
>
{isLoading ? 'Loading...' : 'View Profile'}
</button>
);
}
命名约定
// 组件:PascalCase
function UserProfile() { }
function ProductCard() { }
function NavigationMenu() { }
// Props:camelCase
function Button({ onClick, isDisabled, children }) { }
// 事件处理函数:handle + 事件名
function Form() {
const handleSubmit = () => { };
const handleInputChange = () => { };
const handleButtonClick = () => { };
}
// 布尔props:is/has/can前缀
function Modal({ isOpen, hasOverlay, canClose }) { }
// 状态:描述性名称
const [isLoading, setIsLoading] = useState(false);
const [userData, setUserData] = useState(null);
const [errorMessage, setErrorMessage] = useState('');
实际应用示例
1. 完整的用户管理组件
// 主组件
function UserManagement() {
return (
<div className="user-management">
<UserHeader />
<UserFilters />
<UserList />
<UserPagination />
</div>
);
}
// 用户列表组件
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchUsers = useCallback(async () => {
setLoading(true);
try {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;
return (
<div className="user-list">
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
// 用户卡片组件
const UserCard = React.memo(function UserCard({ user }) {
const [isEditing, setIsEditing] = useState(false);
const handleEdit = useCallback(() => {
setIsEditing(true);
}, []);
const handleSave = useCallback(async (updatedUser) => {
try {
await updateUser(updatedUser);
setIsEditing(false);
} catch (error) {
console.error('Failed to update user:', error);
}
}, []);
return (
<div className="user-card">
{isEditing ? (
<UserEditForm
user={user}
onSave={handleSave}
onCancel={() => setIsEditing(false)}
/>
) : (
<UserDisplay
user={user}
onEdit={handleEdit}
/>
)}
</div>
);
});
2. 自定义Hook示例
// 自定义Hook:数据获取
function useDataFetching(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
if (isMounted) {
setData(result);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
// 使用自定义Hook
function ProductList() {
const { data: products, loading, error } = useDataFetching('/api/products');
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;
return (
<div className="product-list">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
总结
记住:小组件 + hooks + 有意义的命名 = 快乐的开发者和可维护的应用!
这些最佳实践将帮助您:
- 编写更干净、更可维护的代码
- 提高应用性能
- 减少bug和调试时间
- 改善团队协作
您最喜欢哪个最佳实践?在下面评论!