React简易购物车数量数据跨组件传递不一致问题求助
我正在学习前端开发,用React写简易购物车练手。修改商品数量时,希望按商品ID生成数量值对象,传递给购物车组件展示。在ProductCard组件里,QTY状态的数量显示正常,但把这个状态通过父组件App传递给ShoppingCart组件后,用console.log查看数据会出现异常:有时完全没更新,有时数值少1。
我知道可以用useContext或Redux这类状态管理工具做全局状态,比子传父再传子方便,但目前不想用这些工具。
组件代码
App组件
import { useState } from "react"; import ProductCard from "./components/ProductCard"; import ShoppingCart from "./components/ShoppingCart"; function App() { let [productData, setProductData] = useState({}); function getProductData(cartData: object) { setProductData(cartData); } return ( <> <div> <ProductCard passProductData={getProductData} /> <ShoppingCart productData={productData} /> </div> </> ); } export default App;
ShoppingCart组件
interface Props { productData: object; } function ShoppingCart({ productData }: Props) { return ( <> <button onClick={() => { console.log(JSON.stringify(productData)); }} className="bg-slate-400" > Test passed data </button> <h1 className="font-bold">Your cart</h1> <p className="font-bold">Order Total</p> </> ); } export default ShoppingCart;
ProductCard组件
import productList from "../data/productList"; import { useState } from "react"; interface Props { passProductData: (quantities: object) => void; } function ProductCard({ passProductData }: Props) { let [newProductList, changeProductList] = useState(productList); let [productQuantities, setProductQuantities] = useState({ 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, }); function revealQuantityInput(id: number) { changeProductList( newProductList.map((product) => { if (product.id === id) { return { ...product, quantityVisibility: true }; } else return product; }) ); } function increaseQuantity(id: number) { for (let [key, value] of Object.entries(productQuantities)) { if (Number(key) === id) { setProductQuantities({ ...productQuantities, [key]: value + 1, }); } } passProductData(productQuantities); } function decreaseQuantity(id: number) { for (let [key, value] of Object.entries(productQuantities)) { if (Number(key) === id) { setProductQuantities({ ...productQuantities, [key]: value > 0 ? value - 1 : value, }); } } passProductData(productQuantities); } function setSpecificQuantityValue(id: number, quantity: number) { newProductList.map((product) => { if (product.id === id) { return setProductQuantities({ ...productQuantities, [product.id]: quantity, }); } }); passProductData(productQuantities); } return ( <> {newProductList.map((product) => ( <div className="p-6 mb-6 [&>*]:mb-4" key={product.id}> <img src={product.image} /> <div className="bg-green-600 p-2" onClick={() => revealQuantityInput(product.id)} > {product.quantityVisibility ? ( <div> <p onClick={() => decreaseQuantity(product.id)}>-</p> <input type="number" id={"quantity" + product.id} min="1" max="9" step="1" onChange={(e) => setSpecificQuantityValue(product.id, Number(e.target.value)) } className="bg-gray-300 w-[150px] opacity-100 mr-3" /> <p onClick={() => increaseQuantity(product.id)}>+</p> </div> ) : ( <p>Add to Cart</p> )} </div> <p>{product.category}</p> <p>{product.name}</p> <p>{product.price}</p> <p>QTY: {JSON.stringify(productQuantities)}</p> </div> ))} </> ); } export default ProductCard;
商品数据
import productTypes from "../types/productTypes" const productList:productTypes[] = [ { id: 0, image: "../../images/image-waffle-mobile.jpg", name: "Waffle with Berries", category: "Waffle", price: 6.50, quantityVisibility: false, }, { id: 1, image: "../../images/image-creme-brulee-mobile.jpg", name: "Vanilla Bean Crème Brûlée", category: "Crème Brûlée", price: 7.00, quantityVisibility: false, }, { id: 2, image: "../../images/image-macaron-mobile.jpg", name: "Macaron Mix of Five", category: "Macaron", price: 8.00, quantityVisibility: false, }, { id: 3, image: "../../images/image-tiramisu-mobile.jpg", name: "Classic Tiramisu", category: "Tiramisu", price: 5.50, quantityVisibility: false, }, { id: 4, image: "../../images/image-baklava-mobile.jpg", name: "Pistachio Baklava", category: "Baklava", price: 4.00, quantityVisibility: false, }, { id: 5, image: "../../images/image-meringue-mobile.jpg", name: "Pistachio Baklava", category: "Baklava", price: 4.00, quantityVisibility: false, }, ] export default productList
问题原因及解决方案
核心问题
React的setState是异步操作,你在调用setProductQuantities后立刻调用passProductData(productQuantities),此时传递的还是更新前的旧状态值,因为状态更新还没完成。这就导致父组件拿到的数据总是慢一步,甚至没更新。
修复方案
1. 用setState回调获取最新状态
修改increaseQuantity、decreaseQuantity、setSpecificQuantityValue这三个函数,在setProductQuantities的回调里调用passProductData,确保传递的是更新后的最新状态:
// 修改increaseQuantity function increaseQuantity(id: number) { setProductQuantities(prev => { const newQuantities = { ...prev }; newQuantities[id] = (newQuantities[id] || 0) + 1; passProductData(newQuantities); return newQuantities; }); } // 修改decreaseQuantity function decreaseQuantity(id: number) { setProductQuantities(prev => { const newQuantities = { ...prev }; newQuantities[id] = Math.max(0, (newQuantities[id] || 0) - 1); passProductData(newQuantities); return newQuantities; }); } // 修改setSpecificQuantityValue function setSpecificQuantityValue(id: number, quantity: number) { setProductQuantities(prev => { const newQuantities = { ...prev }; newQuantities[id] = Math.max(0, quantity); // 确保数量不小于0 passProductData(newQuantities); return newQuantities; }); }
2. 简化状态更新逻辑
去掉原来的for循环遍历对象,直接通过id定位修改,代码更简洁高效。
3. 优化newProductList状态更新
revealQuantityInput里的状态更新改用回调形式,避免直接依赖外部的newProductList:
function revealQuantityInput(id: number) { changeProductList(prevList => prevList.map(product => product.id === id ? { ...product, quantityVisibility: true } : product ) ); }
修复后效果
修改后,每次状态更新完成后才会把最新的数量数据传递给父组件,ShoppingCart组件拿到的就是实时的正确数据,console.log输出也会和ProductCard里的显示一致。
内容的提问来源于stack exchange,提问作者Darius Molotokas

