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

React.js中如何通过加减按钮实现商品总价格计算

问题根因

  • 全局共用单个计数器:你把counter状态定义在BillList组件顶层,所有遍历渲染的商品共享同一个计数器,修改后所有商品的数量会同步变化,这是核心问题
  • 未触发接口拉取逻辑:你在mapDispatchToProps中注册了getItems方法,但组件内没有主动调用,API数据可能根本没有拉取成功
  • 减按钮缺少边界判断:没有限制数量最小值,可能出现负数或0的异常值
  • 不要在React中使用addEventListener绑定DOM事件,React的合成事件onClick本身就是可用的,不需要额外绑定原生事件

修复方案

  1. 调整计数器状态结构,用对象存储每个商品ID对应的独立计数
  2. 组件挂载时调用getItems拉取商品数据
  3. 给减按钮添加边界判断,避免数量小于1
  4. 调整数量展示、总价计算的取值逻辑

修复后的完整代码

import React, { useState, useEffect } from "react";
import "./Bill.css";
import { Link, useParams, Switch, Route } from "react-router-dom";
import { connect } from "react-redux";
import { getItems } from "../store/actions/itemsActions";

// 把Display组件移到外层,避免每次渲染重复创建
function Display(props) {
  return <label style={{ marginLeft: ".5rem" }}>{props.message}</label>;
}

function BillList({ items, getItems }) {
  // 用对象存储每个商品id对应的计数,key是商品id,value是对应数量
  const [counters, setCounters] = useState({});
  const params = useParams();

  // 组件挂载时拉取商品数据
  useEffect(() => {
    getItems();
  }, [getItems]);

  return (
    <div className="bills">
      <div
        className="main-title"
        style={{
          textAlign: "center",
          justifyContent: "center",
          alignItems: "center",
          fontSize: 14,
        }}
      >
        <h1>Bakery</h1>
        <h1>Company Gbr</h1>
        <h1>Oranienburger Straße 120</h1>
        <h1>10119 Berlin</h1>
      </div>
      
      <div className="bills-container">
        <div></div>
        <div className="item-list">
          {items &&
            items.items &&
            items.items.map((item) => {
              // 取当前商品的数量,默认是1
              const currentCount = counters[item.id] || 1;
              return (
                <React.Fragment key={item.id}>
                  <div className="bill-time">
                    <div className="bill">
                      <h4>
                        <strong>Bill: </strong>
                        {item.billNumber}
                      </h4>
                    </div>
                    <div className="time">
                      <h4>
                        <strong>Time: </strong>
                        {item.created_datetime}
                      </h4>
                    </div>
                  </div>
                  ----------------------------------------------------------------
                  <div className="price-total">
                    <div className="title">
                      <h3>
                        <strong>Title: </strong>
                        {item.title}
                      </h3>
                      <div className="counter">
                        <strong><Display message={currentCount}/>x</strong>
                      </div>
                    </div>
                    <div className="increase">
                      <button onClick={() => {
                        setCounters(prev => ({
                          ...prev,
                          [item.id]: (prev[item.id] || 1) + 1
                        }))
                      }}>+</button>
                    </div>
                    <div className="decrease">
                      <button onClick={() => {
                        setCounters(prev => {
                          const curr = prev[item.id] || 1;
                          return {
                            ...prev,
                            [item.id]: curr > 1 ? curr - 1 : 1
                          }
                        })
                      }}>-</button>
                    </div>
                    <div className="price">
                      <h4>
                        <strong>Price: {parseFloat(item.price).toFixed(2)}$</strong>
                      </h4>
                    </div>
                    <div className="total">
                      <h4>
                        Total: {(parseFloat(item.price) * currentCount).toFixed(2)}$
                      </h4>
                    </div>
                  </div>
                </React.Fragment>
              )
            })}
        </div>
      </div>
      <div className="button-path">
        <Link to="/">
          <div className="button">
            <button className="main-button">Analyse Receipt</button>
          </div>
        </Link>
      </div>
      <Switch>
        <Route path="/bills/:id" /> 
      </Switch>
      <div>ID: {params.id}</div>
    </div>
  );
}

const mapStateToProps = (state) => {
  return {
    items: state.items,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    getItems: () => dispatch(getItems()),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(BillList);

额外优化说明

  • 把Display组件移到BillList外部,避免组件每次渲染时重复创建新的函数组件,减少不必要的性能损耗
  • 价格计算后添加toFixed(2)保留两位小数,避免浮点数计算出现多位小数的异常展示
  • 所有商品的计数独立,修改任意商品的数量不会影响其他商品的计算结果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 13:15:03