React项目中如何用真实API数据替换UUID生成的模拟数据并保留productCode作为标识符
没问题,我来帮你把API数据无缝替换掉原来的模拟数据,同时保持现有功能完全正常。核心思路是把API返回的嵌套数据结构转换成你现有组件期望的格式——因为你的组件已经依赖了productCode、value、vendor这些字段,我们只需要做一层数据映射,不用改动组件核心逻辑。
步骤1:替换数据获取逻辑,转换API数据格式
首先,删掉原来的data模拟数据生成代码,把useEffect里设置products的逻辑换成API请求,同时对返回的数据做格式转换:
export default function App() { const [products, setProducts] = useState([]); const [cart, setCart] = useState([]); // 替换模拟数据逻辑,改用API获取并转换格式 useEffect(() => { fetch("http://localhost:1337/api/rows/") .then((response) => response.json()) .then((apiResponse) => { // 将API返回的嵌套结构转换成组件需要的扁平格式 const transformedProducts = apiResponse.data.map(item => ({ productCode: item.attributes.ProductCode, // 对应API的ProductCode value: item.attributes.Description, // 用API的Description作为商品描述 vendor: item.attributes.Vendor, // 用API的Vendor作为供应商 price: item.attributes.DefaultCost, // 用API的DefaultCost作为商品价格 id: item.id // 保留API返回的原始id(可选) })); setProducts(transformedProducts); }) .catch(error => console.error("获取产品数据失败:", error)); }, []); // 下面的handleClick、handleChange逻辑完全保留,不用修改 // ... 原有代码 }
这里的关键是字段精准映射,确保转换后的数据和原来模拟数据的字段名、含义完全一致,这样所有依赖这些字段的组件都能正常工作。
步骤2:修复路由参数的类型匹配问题
API返回的ProductCode是数字类型,但路由参数默认是字符串,所以在ItemDetail组件里需要把参数转成数字才能正确匹配商品:
function ItemDetail({ handleClick, products }) { const { productCode, value, vendor } = useParams(); // 把字符串类型的路由参数转成数字,匹配API返回的productCode类型 const targetProduct = products.find(item => item.productCode === Number(productCode)); return ( <div> <p>product id: {targetProduct.productCode}</p> <p>Description: {targetProduct.value}</p> <p>vendor: {targetProduct.vendor}</p> <button onClick={() => handleClick(targetProduct)}>Add to Cart</button> </div> ); }
另外,如果API返回的商品描述或供应商名称包含空格、特殊字符,建议对路由参数做编码处理,避免路由解析出错:
const Products = ({ products }) => { return ( <> <h1>Products</h1> <ul> {products.map((item) => ( <li key={item.productCode}> <Link to={generatePath( "/itemDetail/:productCode/:value/:vendor", { productCode: item.productCode, value: encodeURIComponent(item.value), vendor: encodeURIComponent(item.vendor) } )} > {item.value} </Link> </li> ))} </ul> </> ); };
步骤3:确认购物车逻辑正常运行
你的购物车逻辑已经用productCode作为唯一标识符,转换后的数据里productCode是业务唯一的(API返回的每个商品ProductCode应该是唯一的),所以handleClick和handleChange完全不需要修改。
唯一的小优化:把Cart组件里的key从item.id改成item.productCode,因为它是业务上的唯一标识,更符合React key的使用规范:
const Cart = ({ cart, setCart, handleChange }) => { // ... 原有逻辑 return ( <article> {cart.map((item) => ( <div className="cart_box" key={item.productCode}> {/* 改用productCode作为key */} {/* ... 原有内容 */} </div> ))} {/* ... 原有内容 */} </article> ); };
完整修改后的App代码
这里整合了所有修改的完整代码:
import "./styles.css"; import { useEffect, useState } from "react"; import { BrowserRouter as Router, Routes, Route, useParams, Link, generatePath } from "react-router-dom"; const Home = () => <h1>Home</h1>; function ItemDetail({ handleClick, products }) { const { productCode, value, vendor } = useParams(); const targetProduct = products.find(item => item.productCode === Number(productCode)); return ( <div> <p>product id: {targetProduct.productCode}</p> <p>Description: {targetProduct.value}</p> <p>vendor: {targetProduct.vendor}</p> <button onClick={() => handleClick(targetProduct)}>Add to Cart</button> </div> ); } const Cart = ({ cart, setCart, handleChange }) => { const handleRemove = (productCode) => { setCart((cart) => cart.filter((item) => item.productCode !== productCode)); }; const price = cart.reduce( (total, item) => total + item.amount * item.price, 0 ); return ( <article> {cart.map((item) => ( <div className="cart_box" key={item.productCode}> <p>{item.value}</p> <div> <button onClick={() => handleChange(item.productCode, 1)}>+</button> <button>{item.amount}</button> <button onClick={() => handleChange(item.productCode, -1)}> - </button> </div> <div> <span>{item.price}</span> <button onClick={() => handleRemove(item.productCode)}> Remove </button> </div> </div> ))} <div className="total"> <span>Total Price of your Cart</span> <span>R - {price}</span> </div> </article> ); }; const Products = ({ products }) => { return ( <> <h1>Products</h1> <ul> {products.map((item) => ( <li key={item.productCode}> <Link to={generatePath( "/itemDetail/:productCode/:value/:vendor", { productCode: item.productCode, value: encodeURIComponent(item.value), vendor: encodeURIComponent(item.vendor) } )} > {item.value} </Link> </li> ))} </ul> </> ); }; export default function App() { const [products, setProducts] = useState([]); const [cart, setCart] = useState([]); useEffect(() => { fetch("http://localhost:1337/api/rows/") .then((response) => response.json()) .then((apiResponse) => { const transformedProducts = apiResponse.data.map(item => ({ productCode: item.attributes.ProductCode, value: item.attributes.Description, vendor: item.attributes.Vendor, price: item.attributes.DefaultCost, id: item.id })); setProducts(transformedProducts); }) .catch(error => console.error("Failed to fetch products:", error)); }, []); const handleClick = (item) => { if (cart.some((cartItem) => cartItem.productCode === item.productCode)) { setCart((cart) => cart.map((cartItem) => cartItem.productCode === item.productCode ? { ...cartItem, amount: cartItem.amount + 1 } : cartItem )); return; } setCart((cart) => [ ...cart, { ...item, amount: 1 } ]); }; const handleChange = (productCode, d) => { setCart((cart) => cart.flatMap((cartItem) => cartItem.productCode === productCode ? cartItem.amount + d < 1 ? [] : [ { ...cartItem, amount: cartItem.amount + d } ] : [cartItem] )); }; return ( <div className="App"> <Router> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/products">Products</Link> </li> <li> <Link to="/cart">Cart</Link> </li> </ul> <Routes> <Route path="/" element={<Home />} /> <Route path="products" element={<Products products={products} />} /> <Route path="/itemDetail/:productCode/:value/:vendor" element={ <ItemDetail products={products} handleClick={handleClick} /> } /> <Route path="/Cart/" element={ <Cart cart={cart} setCart={setCart} handleChange={handleChange} /> } /> </Routes> </Router> </div> ); }
额外注意事项
- 错误处理:API请求添加了
catch分支,方便调试请求失败的情况; - 价格格式化:如果API返回的
DefaultCost是整数,可以用item.price.toFixed(2)把价格格式化为两位小数,提升显示体验; - 加载状态:可以添加一个加载状态组件,在API请求未完成时显示加载提示,避免页面空白。
这样修改后,你的应用就能完全使用API数据,同时原有功能(商品列表、详情、购物车加减/删除、总价计算)都能正常运行。
内容的提问来源于stack exchange,提问作者Manasseh Codes

