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

React.js/Redux购物车:实现增减按钮+可输入数量组件方案问询

问题描述

已有基于Redux的购物车功能,已实现商品数量更新、总价计算逻辑,但当前使用type="number"的输入框存在触屏操作体验差、无法删除初始值1进行手动输入的问题,需要将数量控制改为增减按钮+可手动输入的数量框的形式。

现有代码包含:

  • 工具函数:计算购物车总价、总商品数量
  • Cart组件:渲染购物车列表、处理数量输入交互
  • Redux Action与Reducer:处理商品添加、数量修改、清空购物车逻辑
解决方案

1. 重构数量控制UI

将原单一数字输入框,替换为「减号按钮 + 文本输入框 + 加号按钮」的组合,解决触屏操作不便问题,同时允许用户自由输入数量。

2. 实现增减按钮逻辑

新增数量增减处理函数:

  • 点击减号按钮:若当前数量为1则直接移除商品,否则数量减1
  • 点击加号按钮:数量直接加1
  • 所有数量变更通过Redux的changeItemQuantity Action更新状态

3. 优化输入框校验逻辑

允许用户清空输入框,同时对输入值做严格校验:

  • 空值、非数字、小于1的输入自动修正为1
  • 失焦时再次校验,确保输入值有效

4. 完善Redux状态管理

  • 补充removeItem Action及对应Reducer逻辑,用于删除单个商品
  • 修正initialCart为对象(原代码中Cart实际为对象而非数组,存在类型不一致问题)
  • 在changeItemQuantity的Reducer中添加判断:若数量≤0则直接删除商品
修改后的代码

1. Cart组件修改

import React from 'react';
import "./Cart.css";
import Navbar from './Navbar.jsx';
import Footer from './Footer.jsx';
import { useDispatch, useSelector } from 'react-redux';
import { changeItemQuantity, removeItem, removeAll } from '../features/cart/cartSlice.js';
import { calculateTotal } from '../features/utilities/utilities.js';

export default function Cart() {
  const dispatch = useDispatch();
  const cart = useSelector(state => state.cart);

  // 优化输入值校验:空值/非数字/小于1的输入自动修正为1
  const onInputChangeHandler = (name, input) => {
    let newQuantity = Number(input);
    if (input === '' || isNaN(newQuantity) || newQuantity < 1) {
      newQuantity = 1;
    }
    dispatch(changeItemQuantity(name, newQuantity));
  };

  // 减少数量逻辑
  const decreaseQuantity = (name, currentQuantity) => {
    if (currentQuantity <= 1) {
      dispatch(removeItem(name));
      return;
    }
    dispatch(changeItemQuantity(name, currentQuantity - 1));
  };

  // 增加数量逻辑
  const increaseQuantity = (name, currentQuantity) => {
    dispatch(changeItemQuantity(name, currentQuantity + 1));
  };

  const cartElements = Object.entries(cart).map(createCartItem);
  const total = calculateTotal(cart);
  
  return (
    <>
      <Navbar/>
      <div id="cart-container">
        <ul id="cart-items">
          {cartElements.length === 0 ? <div className="cart-empty">Your cart is empty</div> : cartElements}
        </ul>
        <div className="price-btn-container">
          {cartElements.length > 0 ? (
            <button onClick={() => dispatch(removeAll())} className="remove-all">
              Remove All
            </button>
          ) : null}
          <h3 className="total">
            Total{' '}
            <span className="total-value">
              ${total} 
            </span>
          </h3>
        </div>
      </div>
      <Footer/>
    </>
  );

  function createCartItem([name, item]) {
    return (
      <li className="cart-list" key={name}>
        <img src={item.img}/>
        <p>{name}</p>
        <p>{item.type}</p>
        <div className="quantity-container">
          <p>Quantity:</p>
          {/* 增减按钮+输入框组合 */}
          <div className="quantity-controls">
            <button 
              className="quantity-btn"
              onClick={() => decreaseQuantity(name, item.quantity)}
            >
              -
            </button>
            <input 
              type="text"
              className="item-quantity" 
              name="quantity" 
              value={item.quantity}
              onChange={(e) => onInputChangeHandler(name, e.target.value)}
              onBlur={(e) => onInputChangeHandler(name, e.target.value)}
            />
            <button 
              className="quantity-btn"
              onClick={() => increaseQuantity(name, item.quantity)}
            >
              +
            </button>
          </div>
        </div>
      </li>
    );
  }
};

2. Redux CartSlice修改

export const addItem = (itemToAdd) => {
  return {
    type: 'cart/addItem',
    payload: itemToAdd,
  };
};

export const removeAll = () => {
  return {
    type: 'cart/removeAll',
    payload: null
  }
}

export const removeItem = (itemName) => {
  return {
    type: 'cart/removeItem',
    payload: itemName,
  };
}

export const changeItemQuantity = (name, newQuantity ) => {
 return {
    type: 'cart/changeItemQuantity',
    payload: {
      name,
      newQuantity
    }
  }
}

// 修正initialCart为对象(与实际使用的Cart结构一致)
const initialCart = {};
export const cartReducer = (cart = initialCart, action) => {
  switch (action.type) {
    case 'cart/addItem': {
      const { name, price, img, type } = action.payload;
      const quantity = cart[name] ? cart[name].quantity + 1 : 1;
      const newItem = { price, img, type, quantity };
      return { 
        ...cart, 
        [name]: newItem 
      };
    }
    case 'cart/removeItem': {
      // 删除指定商品
      const newCart = {...cart};
      delete newCart[action.payload];
      return newCart;
    }
    
    case 'cart/removeAll':
      return initialCart;

    case 'cart/changeItemQuantity': {
      const { name, newQuantity } = action.payload;
      // 数量≤0时直接删除商品
      if (newQuantity <= 0) {
        const newCart = {...cart};
        delete newCart[name];
        return newCart;
      }
      const itemToUpdate = cart[name];
      const updatedItem = {
        ...itemToUpdate,
        quantity: newQuantity
      }
      return {
        ...cart,
        [name]: updatedItem 
      }
    }
    default: {
      return cart;
    }
  }
};

3. 工具函数修正(移除重复定义)

export function calculateTotal(cart) {
  let totalUSD = 0;
  Object.keys(cart).forEach((itemName) => {
    totalUSD += cart[itemName].price * cart[itemName].quantity;
  });
  return totalUSD.toFixed(2);
}

export function calculateTotalQuantity(cart) {
  let totalQuantity = 0;
  Object.keys(cart).forEach((itemName) => {
    totalQuantity += cart[itemName].quantity;
  });
  return totalQuantity;
}

4. 可选:CSS样式优化

在Cart.css中添加数量控制组件的样式,提升视觉体验:

.quantity-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.quantity-btn {
  width: 30px;
  height: 30px;
  border: 1px solid #ddd;
  background: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.quantity-btn:hover {
  background: #f5f5f5;
}

.item-quantity {
  width: 50px;
  height: 30px;
  text-align: center;
  border: 1px solid #ddd;
  outline: none;
}

.item-quantity:focus {
  border-color: #007bff;
}

内容的提问来源于stack exchange,提问作者Igor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 19:20:56