You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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ć

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 13:29:06