You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化我的简易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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 22:47:26