电商前台是前端开发中最经典、最综合的业务场景之一。它不仅涉及数据展示、状态同步、用户交互,还要处理复杂的跨页面数据流转和一致性问题。本节将以一个简化的电商应用为例,围绕 商品列表、购物车、下单流程 三个核心模块,讲解如何使用 React 生态进行落地实现。
整体架构与状态设计
在开始编码之前,先明确应用的基本页面结构和数据流:
页面路由:
/商品列表页/cart购物车页/checkout下单结算页/orders订单列表页(可选)
核心状态:
- 商品数据:从服务端获取,组件内部状态或通过 React Query 管理。
- 购物车数据:全局共享,多个页面需要读写,适合用全局状态库(这里选用 Zustand)。
- 用户信息:从登录态获取,可通过 Context 或状态库管理。
技术栈选型:
- 路由:React Router v6
- 数据请求:TanStack Query (React Query) + Axios
- 全局状态:Zustand(购物车)
- 样式:Tailwind CSS(为简洁,示例中省略具体样式类名)
一、商品列表
商品列表是入口页面,需支持商品展示、分类筛选、搜索、分页等功能。这里重点展示列表渲染、加载态处理和加入购物车交互。
1.1 定义商品类型
interface Product {
id: string;
name: string;
price: number;
image: string;
stock: number;
}
1.2 封装数据请求 Hook
使用 React Query 获取商品数据,自动管理加载状态和缓存。
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
const fetchProducts = async (): Promise<Product[]> => {
const { data } = await axios.get('/api/products');
return data;
};
export const useProducts = () => {
return useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
staleTime: 1000 * 60 * 5, // 5分钟内数据视为新鲜
});
};
1.3 商品列表组件
在组件中使用 Hook,区分加载、错误、空数据等状态,最后渲染商品卡片。
import { useProducts } from './useProducts';
import { useCartStore } from './cartStore'; // 后面实现
const ProductList = () => {
const { data: products, isLoading, error } = useProducts();
const addItem = useCartStore(state => state.addItem);
if (isLoading) return <div>加载中...</div>;
if (error) return <div>加载失败</div>;
if (!products?.length) return <div>暂无商品</div>;
return (
<div className="product-grid">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={() => addItem({ ...product, quantity: 1 })}
/>
))}
</div>
);
};
1.4 商品卡片组件
卡片展示信息,并提供“加入购物车”按钮,按钮需考虑库存禁用。
const ProductCard = ({ product, onAddToCart }) => {
const isSoldOut = product.stock <= 0;
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>¥{product.price}</p>
<button
onClick={onAddToCart}
disabled={isSoldOut}
>
{isSoldOut ? '已售罄' : '加入购物车'}
</button>
</div>
);
};
加入购物车直接调用 addItem,全局状态实时更新,无需额外的上下文传递。
二、购物车
购物车需要支持增加/减少商品数量、删除商品、计算总价,并且数据在多个页面间保持一致。使用 Zustand 实现轻量且高性能的全局购物车 Store。
2.1 购物车状态管理(Zustand Store)
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
image: string;
quantity: number;
stock: number;
}
interface CartState {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
totalPrice: () => number;
}
export const useCartStore = create<CartState>()(
persist(
(set, get) => ({
items: [],
addItem: (item) => {
const existing = get().items.find(i => i.id === item.id);
if (existing) {
// 已存在则增加数量,但不超过库存
set({
items: get().items.map(i =>
i.id === item.id
? { ...i, quantity: Math.min(i.quantity + 1, i.stock) }
: i
),
});
} else {
set({ items: [...get().items, { ...item, quantity: 1 }] });
}
},
removeItem: (id) => {
set({ items: get().items.filter(i => i.id !== id) });
},
updateQuantity: (id, quantity) => {
if (quantity < 1) return;
set({
items: get().items.map(i =>
i.id === id
? { ...i, quantity: Math.min(quantity, i.stock) }
: i
),
});
},
clearCart: () => set({ items: [] }),
totalPrice: () => {
return get().items.reduce((sum, i) => sum + i.price * i.quantity, 0);
},
}),
{
name: 'cart-storage', // 本地存储 key,用于持久化
}
)
);
addItem处理重复商品数量叠加(考虑库存上限)。updateQuantity直接设置数量,同样受库存限制。- 使用
persist中间件将购物车数据自动同步到localStorage,刷新页面不丢失。
2.2 购物车页面组件
购物车页面读取全局状态,渲染商品列表、操作按钮和总价区域。
import { useCartStore } from './cartStore';
import { Link } from 'react-router-dom';
const CartPage = () => {
const { items, removeItem, updateQuantity, totalPrice } = useCartStore();
if (items.length === 0) {
return <div>购物车是空的,<Link to="/">去逛逛</Link></div>;
}
return (
<div className="cart-page">
<h2>购物车</h2>
{items.map(item => (
<div key={item.id} className="cart-item">
<img src={item.image} alt={item.name} />
<div>
<h4>{item.name}</h4>
<p>¥{item.price}</p>
<div>
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
disabled={item.quantity <= 1}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
disabled={item.quantity >= item.stock}
>
+
</button>
</div>
</div>
<div>
<p>小计:¥{item.price * item.quantity}</p>
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
</div>
))}
<div className="cart-summary">
<p>合计:¥{totalPrice()}</p>
<Link to="/checkout">去结算</Link>
</div>
</div>
);
};
- 空购物车时引导用户返回商品列表。
- 增减按钮受最小数量(1)和库存限制。
- 总价直接调用 Store 中的方法实时计算。
三、下单流程
下单流程一般涉及:提交订单 → 校验库存 → 生成订单 → 清空购物车 → 跳转到支付或订单成功页。这里是前后端交互的典型场景。
3.1 下单页面与表单
结算页面需要收集收货地址、支付方式等信息,并展示最终要提交的订单摘要。
const CheckoutPage = () => {
const { items, totalPrice, clearCart } = useCartStore();
const navigate = useNavigate();
const [address, setAddress] = useState('');
const [submitting, setSubmitting] = useState(false);
const handleSubmit = async () => {
if (items.length === 0) return;
setSubmitting(true);
try {
await axios.post('/api/orders', {
address,
items: items.map(i => ({ productId: i.id, quantity: i.quantity })),
});
clearCart(); // 下单成功清空购物车
navigate('/orders/success', { state: { message: '下单成功' } });
} catch (error) {
alert('下单失败,请重试');
} finally {
setSubmitting(false);
}
};
return (
<div className="checkout-page">
<h2>订单确认</h2>
<div>
<h3>收货地址</h3>
<textarea
value={address}
onChange={e => setAddress(e.target.value)}
placeholder="请输入详细地址"
/>
</div>
<div>
<h3>商品列表</h3>
{items.map(item => (
<p key={item.id}>{item.name} × {item.quantity} —— ¥{item.price * item.quantity}</p>
))}
</div>
<p>应付总额:¥{totalPrice()}</p>
<button onClick={handleSubmit} disabled={submitting || !address}>
{submitting ? '提交中...' : '确认下单'}
</button>
</div>
);
};
3.2 下单接口设计要点
- 后端需做最终库存校验,防止前端绕过限制。
- 返回订单号,前端可据此展示结果页。
- 失败时返回具体错误码(如库存不足),前端给出友好提示。
3.3 处理下单过程中的异常
在实际项目中,下单可能存在并发、网络中断等问题:
- 防抖:提交按钮在请求期间禁用,防止重复提交。
- 乐观更新:如果希望体验更好,可以先在前端扣减库存显示,但最终以后端校验为准。
- 回滚:若订单创建失败,不做任何本地状态修改。
3.4 下单后流程
下单成功后一般跳转到成功页,可展示订单号、预计配送等信息。
const OrderSuccessPage = () => {
const location = useLocation();
const message = location.state?.message ?? '下单成功';
return <div>{message},感谢您的购买!</div>;
};
同时也可以提供查看订单详情的链接。
完整全链路数据流回顾
- 商品列表通过 React Query 异步获取,缓存优化性能。
- 加入购物车触发 Zustand Store 的
addItem,数据立即同步到全局状态,并由persist中间件自动持久化到本地存储。 - 购物车页面读取 Store 中的
items,渲染、修改数量、删除,变化实时反映。 - 下单页面读取购物车数据展示订单摘要,收集收货信息,调用下单接口;成功后清空购物车并跳转。
- 所有组件均通过响应式状态驱动,无需手动同步,数据流向清晰可预测。
这种架构简洁且可扩展,适用于从个人项目到中大型电商的开发。通过合理拆分职责(展示、状态管理、异步请求),你能在高内聚低耦合的前提下快速迭代功能。