React.js中如何通过加减按钮实现商品总价格计算
问题根因
- 全局共用单个计数器:你把
counter状态定义在BillList组件顶层,所有遍历渲染的商品共享同一个计数器,修改后所有商品的数量会同步变化,这是核心问题 - 未触发接口拉取逻辑:你在
mapDispatchToProps中注册了getItems方法,但组件内没有主动调用,API数据可能根本没有拉取成功 - 减按钮缺少边界判断:没有限制数量最小值,可能出现负数或0的异常值
- 不要在React中使用
addEventListener绑定DOM事件,React的合成事件onClick本身就是可用的,不需要额外绑定原生事件
修复方案
- 调整计数器状态结构,用对象存储每个商品ID对应的独立计数
- 组件挂载时调用
getItems拉取商品数据 - 给减按钮添加边界判断,避免数量小于1
- 调整数量展示、总价计算的取值逻辑
修复后的完整代码
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
相关产品推荐
相关产品推荐

