如何优化我的简易React购物清单应用?
购物清单应用改进建议
一、代码结构优化
1. 拆分状态管理逻辑
将App组件中的数据处理逻辑抽离为独立Hook,让组件仅负责路由与渲染,职责更清晰:
// hooks/useShoppingList.ts import { useState } from 'react'; import type { Item } from '../types'; export const useShoppingList = (initialItems: Item[]) => { const [items, setItems] = useState<Item[]>(initialItems); const [nextId, setNextId] = useState(Math.max(...initialItems.map(i => i.id), 0) + 1); const handleSaveItem = (savedItem: Item) => { setItems(prev => { if (savedItem.id) { return prev.map(i => i.id === savedItem.id ? savedItem : i); } const newItem = { ...savedItem, id: nextId }; setNextId(prev => prev + 1); return [...prev, newItem]; }); }; const handleDeleteItem = (id: number) => { setItems(prev => prev.filter(i => i.id !== id)); }; return { items, handleSaveItem, handleDeleteItem }; };
在App.tsx中使用该Hook:
import { useShoppingList } from './hooks/useShoppingList'; function App() { const { items, handleSaveItem, handleDeleteItem } = useShoppingList(SEED_DATA); // 其余代码保持不变 }
2. 精简ItemForm的Props依赖
编辑场景下不再传递整个items数组,直接传入目标item,减少组件不必要的依赖:
// App.tsx 编辑路由 <Route path="/edit/:id" element={ <ItemForm itemToEdit={items.find(i => i.id === Number(useParams().id))} onSave={handleSaveItem} /> } />
修改ItemForm的Props与初始化逻辑:
interface ItemFormProps { itemToEdit?: Item; onSave: (item: Item) => void; } export default function ItemForm({ itemToEdit, onSave }: ItemFormProps) { const navigate = useNavigate(); const isEdit = !!itemToEdit; const [name, setName] = useState(isEdit ? itemToEdit.name : ''); const [quantity, setQuantity] = useState<number>(isEdit ? itemToEdit.quantity : 1); const [categoryId, setCategoryId] = useState<number>(isEdit ? itemToEdit.categoryId : 1); // 移除原有的useEffect,直接通过初始state赋值 // 其余逻辑保持不变 }
3. 优化分类数据结构
将分类改为数组形式,同时生成映射表,便于后续维护:
// types.ts export interface Category { id: number; name: string; } export const CATEGORIES: Category[] = [ { id: 1, name: "食品" }, { id: 2, name: "家居" }, { id: 3, name: "其他" }, ]; export const CATEGORY_MAP = Object.fromEntries( CATEGORIES.map(c => [c.id, c.name]) ) as Record<number, string>;
二、最佳实践改进
1. 增强表单验证与错误提示
添加更完善的表单校验逻辑,并显示友好的错误提示:
// ItemForm.tsx const [errors, setErrors] = useState<Record<string, string>>({}); const validate = () => { const newErrors: Record<string, string> = {}; if (!name.trim()) newErrors.name = '请输入商品名称'; if (quantity <= 0) newErrors.quantity = '数量必须大于0'; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = (e: FormEvent) => { e.preventDefault(); if (!validate()) return; // 保存逻辑保持不变 };
在表单中渲染错误信息:
<div className="form-group"> <label>名称</label> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="商品名称" /> {errors.name && <p className="error-text">{errors.name}</p>} </div>
2. 添加404页面与路由守卫
创建NotFound组件处理无效路由:
// NotFound.tsx import { Link } from 'react-router-dom'; export default function NotFound() { return ( <div className="not-found"> <h2>页面不存在</h2> <Link to="/" className="btn-primary">返回首页</Link> </div> ); }
在App.tsx中添加404路由:
<Route path="*" element={<NotFound />} />
在ItemForm中添加路由守卫,防止访问无效编辑ID:
// ItemForm.tsx useEffect(() => { if (isEdit && !itemToEdit) { navigate('/404'); } }, [isEdit, itemToEdit, navigate]);
三、样式优化
1. 迁移内联样式到CSS文件
将组件中的内联样式全部移至App.css,使用类名统一管理:
/* App.css */ .list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .form-group { margin-bottom: 10px; } .form-group label { display: block; margin-bottom: 4px; } .form-actions { display: flex; gap: 10px; margin-top: 16px; } .error-text { color: #ff4d4d; margin: 4px 0 0 0; font-size: 0.9rem; } .cancel-link { padding: 5px 15px; text-decoration: none; background-color: #2c2c2c; color: #e0e0e0; border: 1px solid #444; border-radius: 4px; transition: background-color 0.2s; } .cancel-link:hover { background-color: #3a3a3a; }
2. 添加响应式适配
让表格在小屏幕下自动适配:
/* App.css */ @media (max-width: 600px) { table, thead, tbody, th, td, tr { display: block; } thead tr { position: absolute; top: -9999px; left: -9999px; } tr { border: 1px solid #333; margin-bottom: 1rem; border-radius: 8px; padding: 10px; } td { border-bottom: none; position: relative; padding-left: 50%; padding-top: 8px; padding-bottom: 8px; } td:before { position: absolute; top: 8px; left: 12px; width: 45%; padding-right: 10px; white-space: nowrap; content: attr(data-label); font-weight: 600; color: #aaa; } }
为表格单元格添加data-label属性:
<td data-label="#">{index + 1}</td> <td data-label="数量">{item.quantity}x</td> <td data-label="名称">{item.name}</td> <td data-label="分类">...</td> <td data-label="操作">...</td>
四、性能优化
1. 使用React.memo减少重渲染
为纯组件添加React.memo,避免父组件状态变化时的无意义重渲染:
// ShoppingList.tsx import { memo } from 'react'; export default memo(function ShoppingList({ items, onRemove }: ShoppingListProps) { // 组件内容保持不变 }); // ItemForm.tsx import { memo } from 'react'; export default memo(function ItemForm({ itemToEdit, onSave }: ItemFormProps) { // 组件内容保持不变 });
2. 缓存重复计算结果
将分类选项的生成逻辑缓存,避免每次渲染重复执行:
// ShoppingList.tsx import { useMemo } from 'react'; export default memo(function ShoppingList({ items, onRemove }: ShoppingListProps) { const categoryOptions = useMemo(() => { return CATEGORIES.map(c => ( <option key={c.id} value={c.id}>{c.name}</option> )); }, []); // 渲染时直接使用categoryOptions return ( // ... <select value={item.categoryId} disabled> {categoryOptions} </select> // ... ); });
内容的提问来源于stack exchange,提问作者user32654922
相关产品推荐
相关产品推荐

