React.js实现Checkbox全选功能及选中商品总价计算
React购物车复选框全选功能实现方案
现有代码问题修复
先修正原有逻辑里的几个影响功能的bug:
- 单行复选框错误使用
value属性绑定选中状态,应改为checked属性 - 全选复选框事件、属性名不符合原生input规范,应使用
onChange绑定事件、checked绑定选中状态 - 状态更新直接修改原数组不符合React不可变数据原则,统一改为函数式更新+filter/扩展运算符实现
getCheckedStatus判断逻辑错误,原逻辑found !== null在找不到元素(返回undefined)时会误判为选中,改为用some直接判断存在性
核心实现逻辑
1. 提取可勾选商品列表
用useMemo缓存所有库存>0的非售罄商品,避免每次渲染重复遍历计算:
const selectableItems = useMemo(() => { const items = []; cartData.Result.forEach(shop => { shop.ShopCartList.forEach(cart => { cart.productCartList.forEach(product => { if (product.Stock > 0) items.push(product); }); }); }); return items; }, [cartData]);
2. 双向同步全选状态
新增副作用监听选中项、可选项变化,自动更新全选框状态:当所有非售罄商品都被选中时,全选框自动置为选中,任意一项取消选中则全选框同步取消:
useEffect(() => { const isAllSelected = selectableItems.every(item => checkedItems.some(checked => checked.ProductID === item.ProductID && checked.VariationID === item.VariationID ) ); setIsCheckAll(isAllSelected); }, [checkedItems, selectableItems]);
3. 实现全选/取消全选方法
点击全选框时,若为选中操作则把所有可勾选商品加入选中列表,若为取消操作则清空所有选中项:
const handleSelectAll = (e) => { const isChecked = e.target.checked; if (isChecked) { // 全选:组装所有可选项的选中数据 const allCheckedItems = selectableItems.map(item => ({ ProductID: item.ProductID, VariationID: item.VariationID, Count: item.Count })); setCheckedItems(allCheckedItems); } else { // 取消全选:清空选中列表 setCheckedItems([]); } };
4. 实时计算已选商品总价
用useMemo缓存总价,选中项变化时自动重新计算:
const totalPrice = useMemo(() => { return checkedItems.reduce((sum, checkedItem) => { // 匹配对应商品获取单价,Price字段可根据实际接口返回字段替换 let targetProduct = null; cartData.Result.forEach(shop => { shop.ShopCartList.forEach(cart => { const found = cart.productCartList.find(p => p.ProductID === checkedItem.ProductID && p.VariationID === checkedItem.VariationID ); if (found) targetProduct = found; }); }); return targetProduct ? sum + targetProduct.Price * checkedItem.Count : sum; }, 0); }, [checkedItems, cartData]);
修正后完整代码
import "./styles.css"; import React, { useEffect, useState, useMemo } from "react"; // 此处替换为实际购物车数据 const cartData = { Result: [] }; export default function App() { const [isCheckAll, setIsCheckAll] = useState(false); const [checkedItems, setCheckedItems] = useState( JSON.parse(localStorage.getItem("checkedItems") || "[]") ); // 选中状态持久化 useEffect(() => { localStorage.setItem("checkedItems", JSON.stringify(checkedItems)); }, [checkedItems]); // 提取所有可勾选(非售罄)商品 const selectableItems = useMemo(() => { const items = []; cartData.Result.forEach(shop => { shop.ShopCartList.forEach(cart => { cart.productCartList.forEach(product => { if (product.Stock > 0) items.push(product); }); }); }); return items; }, [cartData]); // 自动同步全选状态 useEffect(() => { const isAllSelected = selectableItems.every(item => checkedItems.some(checked => checked.ProductID === item.ProductID && checked.VariationID === item.VariationID ) ); setIsCheckAll(isAllSelected); }, [checkedItems, selectableItems]); // 计算已选总价 const totalPrice = useMemo(() => { return checkedItems.reduce((sum, checkedItem) => { let targetProduct = null; cartData.Result.forEach(shop => { shop.ShopCartList.forEach(cart => { const found = cart.productCartList.find(p => p.ProductID === checkedItem.ProductID && p.VariationID === checkedItem.VariationID ); if (found) targetProduct = found; }); }); return targetProduct ? sum + targetProduct.Price * checkedItem.Count : sum; }, 0); }, [checkedItems, cartData]); // 添加选中项 const addChecked = (itemId, variationId, qty) => { setCheckedItems(prev => [ ...prev, { ProductID: itemId, VariationID: variationId, Count: qty } ]); }; // 移除选中项 const removeChecked = (itemId, variationId) => { setCheckedItems(prev => prev.filter( item => !(item.ProductID === itemId && item.VariationID === variationId) )); }; // 获取单行选中状态 const getCheckedStatus = (itemId, variationId) => { return checkedItems.some( (item) => item.ProductID === itemId && item.VariationID === variationId ); }; // 全选/取消全选处理 const handleSelectAll = (e) => { const isChecked = e.target.checked; if (isChecked) { const allCheckedItems = selectableItems.map(item => ({ ProductID: item.ProductID, VariationID: item.VariationID, Count: item.Count })); setCheckedItems(allCheckedItems); } else { setCheckedItems([]); } }; return ( <div className="App"> {cartData.Result.map((shop) => shop.ShopCartList.map((cart) => ( <div key={cart.ShopID} md="12" lg="12"> {cart.productCartList.map((items) => { return ( <div key={items.VariationID} md="12" lg="12"> <div id="additem" className="pt-5"> {items.Stock === 0 ? ( <h6 className="bg-light text-danger font-weight-bold "> SOLD OUT </h6> ) : ( <input type="checkbox" checked={getCheckedStatus( items.ProductID, items.VariationID )} onChange={(e) => { if (e.target.checked) { addChecked( items.ProductID, items.VariationID, items.Count ); } else { removeChecked( items.ProductID, items.VariationID ); } }} /> )} {/* 商品其他信息展示 */} </div> </div> ); })} </div> )) )} <div> <input type="checkbox" name="selectAll" id="selectAll" onChange={handleSelectAll} checked={isCheckAll} /> Select All </div> {/* 总价展示 */} <div className="total-price"> 已选商品总价:<em>¥{totalPrice.toFixed(2)}</em> </div> </div> ); }
注意事项
- 代码中
cartData为占位变量,需替换为实际接口返回的购物车数据 - 总价计算中使用的
Price字段为通用命名,需根据实际接口返回的商品单价字段名替换 - 售罄商品(Stock=0)会自动排除在全选范围外,不参与总价计算
内容的提问来源于stack exchange,提问作者Lucky
相关产品推荐
相关产品推荐

