今日学习React的useReducer,实现了一个购物车功能
文章目录
目录
效果展示
逻辑代码
CSS代码
效果展示
逻辑代码
import {useReducer} from "react";
import './index.css';
import { message} from 'antd';
export function ShoppingCount(){
// 初始化购物车数据,包含商品的基本信息如名称、图片、价格、数量和是否选中
const initData = [
{id: 1, name: "精匠传成摇椅躺椅懒人休闲家用加厚", image: "https://img14.360buyimg.com/jdcms/s460x460_jfs/t1/122203/6/41238/105007/65a546caF3decd4f8/ad98fa84a99bc4ec.jpg.avif", price: 10, count: 1, isFlag: true},
{id: 2, name: "HUANWE2024新款旗舰手机", image: "https://img13.360buyimg.com/jdcms/s460x460_jfs/t1/246297/2/16373/35951/66c8a072Fc02c9f02/b2a6cefc921fcefd.jpg.avif", price: 1199, count: 1, isFlag: false},
{id: 3, name: "四川爱媛38号果冻橙", image: "https://img20.360buyimg.com/jdcms/s460x460_jfs/t1/96363/40/52840/128397/67122eedF2da2fb38/46274ae050a78353.jpg.avif", price: 17.9, count: 1, isFlag: true}
];
// 定义购物项类型,用于购物车中每个商品的信息结构
type ShoppingItem = {
isFlag: boolean; // 商品是否被选中
id: number, // 商品ID
name: string, // 商品名称
image: string, // 商品图片URL
price: number, // 商品价格
count: number // 商品数量
}
// 定义购物车操作接口,用于指定对购物车商品的操作类型和受影响的商品ID
interface Action {
type: "ADD" | "SUB" | 'DELETE' | 'isFlag' // 操作类型:添加、减少、删除商品或改变商品选中状态
id: number // 操作针对的商品ID
}
/**
* 购物项 reducer 函数,用于更新购物项状态
* @param state 当前的购物项状态,是一个 ShoppingItem 数组
* @param action 要处理的动作,包含类型和购物项 ID
* @returns 返回新的购物项状态数组
*/
const reducer = (state: ShoppingItem[], action: Action) => {
// 查找与 action.id 匹配的购物项
const item = state.find((item) => item.id === action.id);
// 如果找不到匹配的购物项,则直接返回当前状态
if (item == null) {
return state;
}
// 根据 action.type 执行相应的操作
switch (action.type) {
case "ADD":
// 增加购物项的数量
item.count++;
return [...state];
case "SUB":
// 减少购物项的数量,但至少保持为 1
// 减少购物项的数量,但至少保持为 1
if (item.count > 1) {
item.count--;
}
return [...state];
case "DELETE":
// 删除购物项
return state.filter((item) => item.id !== action.id);
case "isFlag":
// 切换购物项的选中状态
item.isFlag = !item.isFlag;
return [...state];
default:
// 对于未知的操作,直接返回当前状态
return state;
}
}
const [state, dispatch] = useReducer(reducer, initData);
//计算总价
const totalPrice = state.filter(item => item.isFlag).reduce((acc, item) => acc + item.price * item.count, 0);
return (
<div className="cart-container">
<h2>购物车</h2>
<div className="cart-items">
{
state.map((item: ShoppingItem) => {
return (
<div className="cart-item" key={item.id}>
<label className="custom-checkbox">
<input type="checkbox" checked={item.isFlag} onChange={() => {
dispatch({type: "isFlag", id: item.id});
}}/>
<span className="checkmark"></span>
</label>
<img src={item.image} alt={item.name}/>
<div className="item-details">
<h3>{item.name}</h3>
<p className="price">价格: ¥{item.price}</p>
<div className="quantity-controls">
//自减
<button className="minus" onClick={() => {
dispatch({type: "SUB", id: item.id});
}}>-
</button>
//数量
<span className="count">{item.count}</span>
//自增
<button className="plus" onClick={() => {
dispatch({type: "ADD", id: item.id});
}}>+
</button>
</div>
</div>
//删除按钮
<button className="remove-btn" onClick={() => dispatch({type: "DELETE", id: item.id})}>删除
</button>
</div>
)
})
}
</div>
<div className="cart-summary">
<p>总金额:
<span className="total-price">
¥{totalPrice}
</span>
</p>
//结算按钮
<button className="checkout-btn" onClick={() => {
message.success({
type: 'success',
content: '总价格为¥'+totalPrice,
});
}}>去结算</button>
</div>
</div>
)
}
CSS代码
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
body {
background-color: #f4f4f4;
padding: 20px;
}
.cart-container {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
margin-bottom: 20px;
font-size: 24px;
}
.cart-items {
margin-bottom: 20px;
}
.cart-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 0;
border-bottom: 1px solid #ddd;
}
.cart-item img {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 8px;
margin-right: 20px;
}
.item-details {
flex-grow: 1;
}
.item-details h3 {
font-size: 18px;
margin-bottom: 10px;
}
.price {
font-size: 16px;
color: #333;
margin-bottom: 10px;
}
.quantity-controls {
display: flex;
align-items: center;
}
.quantity-controls button {
width: 30px;
height: 30px;
background-color: #f4f4f4;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 18px;
line-height: 30px;
cursor: pointer;
}
.quantity-controls .count {
margin: 0 10px;
font-size: 16px;
}
.remove-btn {
background-color: #ff4c4c;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.remove-btn:hover {
background-color: #e60000;
}
.cart-summary {
text-align: right;
padding-top: 10px;
}
.cart-summary .total-price {
font-weight: bold;
font-size: 18px;
color: #333;
}
.checkout-btn {
background-color: #ff8c00;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.checkout-btn:hover {
background-color: #ff6500;
}
/* 自定义单选框样式 */
.custom-checkbox {
position: relative;
padding-left: 25px;
margin-right: 20px;
cursor: pointer;
}
.custom-checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.custom-checkbox .checkmark {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
background-color: #ccc;
border-radius: 50%;
}
.custom-checkbox input:checked ~ .checkmark {
background-color: #4CAF50;
}
.custom-checkbox .checkmark:after {
content: "";
position: absolute;
display: none;
}
.custom-checkbox input:checked ~ .checkmark:after {
display: block;
}
.custom-checkbox .checkmark:after {
left: 7px;
top: 4px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}