React中获取Map遍历组件的State值及实现购物车动态总价
React购物车:获取子组件状态与动态计算总价方案
问题解答
1. 获取Map遍历生成组件的State值
React中子组件的内部State是封装的,父组件无法直接读取。核心解决思路是状态提升:将子组件的状态转移到父组件统一管理,通过props传递更新函数给子组件,子组件触发状态变更时调用父函数,父组件就能同步获取所有子组件的状态。
另外,你已经引入Redux,也可以将购物车状态存入Redux Store,这是电商购物车场景的常用方案,所有组件都能通过useSelector获取最新状态。
2. 动态计算购物车总价的最佳方案
结合你的场景,推荐两种方案:
方案一:基于Redux管理状态(推荐,适合中大型项目)
利用Redux维护每个商品的数量,实时计算总价:
- Counter组件通过Redux的
dispatch触发加减动作,更新Store中的商品数量 - Products组件通过
useSelector获取Store数据,计算总价
方案二:状态提升到父组件(适合小型项目)
将所有商品的数量状态放到父组件,通过props传递更新函数给Counter,父组件根据最新数量计算总价。
代码实现
方案一:Redux版本代码
修改后的Counter组件
import React from 'react' import { HiOutlineMinus } from "react-icons/hi" import { GoPlus } from "react-icons/go" import { incrementItem, decrementItem } from '../../redux/cart/changeCartNumber' import { useSelector, useDispatch } from 'react-redux' const Counter = ({ stock, _id }) => { const dispatch = useDispatch() // 从Redux Store获取当前商品的数量 const cartItem = useSelector(state => state.cart.items.find(item => item._id === _id)) const value = cartItem?.quantity || 1 return ( <div className='flex font-bold text-xl justify-end'> <div className=' flex gap-4 bg-white p-2 w-40 justify-between '> <button onClick={() => dispatch(decrementItem({ _id }))} disabled={value === 1} className={`${value === 1 && "text-gray-400"}`} > <HiOutlineMinus /> </button> <div>{value}</div> <button onClick={() => dispatch(incrementItem({ _id }))} disabled={value === stock} className={`${value === stock && "text-gray-400"}`} > <GoPlus /> </button> </div> </div> ) } export default Counter
修改后的Products组件
import React from 'react' import { RiDeleteBin6Line } from "react-icons/ri" import Counter from '../../Components/Counter/Counter' import { useSelector } from 'react-redux' const Products = ({ cartdetails }) => { // 从Redux Store获取购物车数据并计算总价 const cartItems = useSelector(state => state.cart.items) const total = cartItems.reduce((sum, item) => { const product = cartdetails.find(detail => detail._id === item._id) return sum + (product?.productdetails.price || 0) * item.quantity }, 0) return ( <div className=' grid grid-cols-12 gap-4'> {cartdetails.length === 0 ? <div>No items in cart</div> : <div className=' col-span-8'> { cartdetails.map((data) => ( <div key={data._id} className=' bg-gray-100 px-4 my-4 py-4'> <div className=' flex justify-between'> <img className=' w-24 h-24' src={data.productdetails.image} alt={data.productdetails.name} /> <div className=''> <div className=' font-semibold text-lg'> {data.productdetails.name} </div> <div className=' text-gray-400 text-sm'> Color : {data.cartItems.color} </div> </div> <div> <div className='font-semibold text-lg'>${data.productdetails.price} USD</div> <div className='text-gray-400 text-sm'>Price</div> </div> <div> <div className=' font-semibold text-lg'>{data.productdetails.stock}</div> <div className='text-gray-400 text-sm'>In Stock</div> </div> <button className=' h-fit text-gray-400'> <RiDeleteBin6Line /> </button> </div> <Counter stock={data.productdetails.stock} _id={data._id} /> </div> )) } </div> } <div className=' col-span-4 my-4 '> <div className=' px-4 py-8 flex flex-col gap-8 bg-gray-100'> <div className=' flex justify-between'> <div className=' font-bold'>Total</div> <div className=' font-semibold'>${total.toFixed(2)}USD</div> </div> <div className=' flex justify-end '> <button className=' btn w-36 font-bold'>Checkout</button> </div> </div> </div> </div> ) } export default Products
方案二:状态提升版本代码
修改后的Products组件
import React, { useState } from 'react' import { RiDeleteBin6Line } from "react-icons/ri" import Counter from '../../Components/Counter/Counter' const Products = ({ cartdetails }) => { // 初始化数量状态,用商品_id作为唯一标识 const [quantities, setQuantities] = useState(() => { const initialState = {} cartdetails.forEach(item => { initialState[item._id] = item.cartItems.quantity }) return initialState }) // 统一更新数量的函数 const updateQuantity = (productId, newQuantity) => { setQuantities(prev => ({ ...prev, [productId]: newQuantity })) } // 实时计算总价 const total = cartdetails.reduce((sum, item) => { const qty = quantities[item._id] || item.cartItems.quantity return sum + item.productdetails.price * qty }, 0) return ( <div className=' grid grid-cols-12 gap-4'> {cartdetails.length === 0 ? <div>No items in cart</div> : <div className=' col-span-8'> { cartdetails.map((data) => ( <div key={data._id} className=' bg-gray-100 px-4 my-4 py-4'> <div className=' flex justify-between'> <img className=' w-24 h-24' src={data.productdetails.image} alt={data.productdetails.name} /> <div className=''> <div className=' font-semibold text-lg'> {data.productdetails.name} </div> <div className=' text-gray-400 text-sm'> Color : {data.cartItems.color} </div> </div> <div> <div className='font-semibold text-lg'>${data.productdetails.price} USD</div> <div className='text-gray-400 text-sm'>Price</div> </div> <div> <div className=' font-semibold text-lg'>{data.productdetails.stock}</div> <div className='text-gray-400 text-sm'>In Stock</div> </div> <button className=' h-fit text-gray-400'> <RiDeleteBin6Line /> </button> </div> <Counter count={quantities[data._id]} stock={data.productdetails.stock} _id={data._id} onUpdate={updateQuantity} /> </div> )) } </div> } <div className=' col-span-4 my-4 '> <div className=' px-4 py-8 flex flex-col gap-8 bg-gray-100'> <div className=' flex justify-between'> <div className=' font-bold'>Total</div> <div className=' font-semibold'>${total.toFixed(2)}USD</div> </div> <div className=' flex justify-end '> <button className=' btn w-36 font-bold'>Checkout</button> </div> </div> </div> </div> ) } export default Products
修改后的Counter组件
import React from 'react' import { HiOutlineMinus } from "react-icons/hi" import { GoPlus } from "react-icons/go" const Counter = ({ count, stock, _id, onUpdate }) => { const handleDecrement = () => { if (count > 1) { onUpdate(_id, count - 1) } } const handleIncrement = () => { if (count < stock) { onUpdate(_id, count + 1) } } return ( <div className='flex font-bold text-xl justify-end'> <div className=' flex gap-4 bg-white p-2 w-40 justify-between '> <button onClick={handleDecrement} disabled={count === 1} className={`${count === 1 && "text-gray-400"}`} > <HiOutlineMinus /> </button> <div>{count}</div> <button onClick={handleIncrement} disabled={count === stock} className={`${count === stock && "text-gray-400"}`} > <GoPlus /> </button> </div> </div> ) } export default Counter
内容的提问来源于stack exchange,提问作者Dhiran Sapkota
相关产品推荐
相关产品推荐

