如何在React+TypeScript电商项目中实现商品详情页的动态内容切换?
当然可以实现!这正是React Router这类路由库设计的核心场景之一——用单个组件动态匹配不同路由参数,渲染对应内容,完全不用为每个商品单独建页面。下面是具体的实现步骤,结合你的代码来调整:
实现步骤
1. 修改Home组件的链接,传递商品ID到路由参数
在Home.tsx里,把每个商品的Link的to属性改成带商品ID的动态路径,这样点击不同商品时,路由会带上对应的ID:
// Home.tsx 中修改Link部分 <Link key={item.id} style={{ textDecoration: "none", color: "black", }} to={`/product/${item.id}`} > {/* 原有商品卡片内容保持不变 */} </Link>
2. 配置动态路由规则
在你的路由配置文件(比如App.tsx或者专门的路由管理文件)中,将Product组件的路由路径设置为动态参数形式,让React Router能够捕获到商品ID:
// 示例:App.tsx中的路由配置 import { Routes, Route } from 'react-router-dom'; import { Home } from './components/Home'; import { Product } from './components/Product'; function App() { return ( <div className="App"> <Routes> <Route path="/" element={<Home />} /> {/* 用:productId定义动态路由参数 */} <Route path="/product/:productId" element={<Product />} /> </Routes> </div> ); } export default App;
3. 在Product组件中获取路由参数并渲染对应商品
在Product.tsx中,使用React Router的useParams钩子获取路由中的商品ID,然后从items数组中匹配对应的商品数据。同时处理商品不存在的边界情况:
首先可以定义商品的TypeScript类型(可选但推荐,让代码更严谨):
// 可放在单独的types文件中,或直接在Product.tsx内定义 type Item = { id: number; name: string; price: number; imgUrl: string; };
然后修改Product组件:
import items from '../../data/items.json' import '../../style/style.css' import { AiFillStar, AiOutlineStar, AiOutlineMinus, AiOutlinePlus, } from "react-icons/ai"; // 导入useParams钩子 import { useParams } from 'react-router-dom'; // 定义商品类型 type Item = { id: number; name: string; price: number; imgUrl: string; }; export function Product(){ // 获取路由参数,注意productId默认是字符串类型 const { productId } = useParams<{ productId: string }>(); // 转换为数字后匹配对应商品 const product = items.find((item: Item) => item.id === Number(productId)); // 处理商品不存在的情况,避免页面报错 if (!product) { return <div style={{ textAlign: 'center', marginTop: '50px', fontFamily: 'monospace', fontSize: '20px' }}>商品不存在或已下架!</div>; } return ( <div style={{ display: "grid", placeContent: "center" }}> <div style={{ display: "flex", flexWrap: "wrap", gap: "20px", }} > <div className="product-details-container" style={{ display: "flex", fontFamily: "monospace", flexDirection: "column", fontSize: "25px", padding: "10px", borderRadius: "10px", }} > <img src={product.imgUrl} width="300px" alt={product.name} /> </div> <div style={{display: 'flex', flexDirection: 'column'}}> <p style={{ fontFamily: "monospace", width: "300px", textAlign: "left", fontSize: "20px", color: "#22223b", }} > {product.name} </p> <div style={{ color: "#ba181b", display: "flex", alignItems: "center" }} > <AiFillStar /> <AiFillStar /> <AiFillStar /> <AiFillStar /> <AiOutlineStar /> <p style={{ marginLeft: "5px", color: "#22223b" }}>(20)</p> </div> <p style={{fontFamily: 'monospace', fontSize: '20px'}}>${product.price}</p> <div style={{ fontFamily: "monospace", color: "#22223b", fontSize: "17px", }} > <p>Details:</p> <p>Cool headphones, amazing sound.</p> </div> <div style={{ display: "flex", alignItems: "center", fontFamily: "monospace", fontSize: "16px", }} > <p style={{ marginRight: "20px" }}>Quantity:</p> <button style={{ background: "transparent", border: "1px solid grey", display: "grid", placeContent: "center", paddingInline: "20px", height: "50px", }} > <AiOutlineMinus /> </button> <p style={{ background: "transparent", border: "1px solid grey", display: "grid", placeContent: "center", paddingInline: "15px", height: "48px", }} > 0 </p> <button style={{ background: "transparent", border: "1px solid grey", display: "grid", placeContent: "center", paddingInline: "20px", height: "50px", }} > <AiOutlinePlus /> </button> </div> <button style={{ background: "transparent", border: "1px solid darkred", color: "darkred", padding: '10px', fontFamily: 'monospace', fontSize: '17px', width: '150px', alignSelf: 'center' }} > Add to Cart </button> </div> </div> </div> ); }
补充说明
- 路由参数
productId默认是字符串类型,所以需要用Number()转换为数字,才能和items数组中数字类型的id匹配。 - 增加了商品不存在的判断逻辑,避免因参数错误导致页面渲染失败,提升用户体验。
- 用TypeScript定义
Item类型,能获得更好的代码提示,减少类型相关的潜在错误。
这样修改后,点击Home页的任意商品,Product组件都会根据路由中的ID动态渲染对应商品的详情——不管你有10个还是1000个商品,只用这一个组件就足够了!
内容的提问来源于stack exchange,提问作者Lazar Panović
相关产品推荐
相关产品推荐

