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

React Hooks实现类组件转函数组件并遍历数组查找商品id

商品详情页类组件转Hooks函数组件方案

需求说明

现有商品卡片列表页,点击任意商品卡片跳转对应商品详情页,需要将原有类组件改写为React Hooks函数组件。

用到的静态数据(products.json)

{
  "shoes": {
    "items": [
      {
        "id": 1,
        "name": "Massimo Dutti真皮运动鞋",
        "price": 135,
        "currency": "LEI",
        "image": "https://i.imgur.com/Rz8lVQa.jpg",
        "size": 45,
        "colour": "黑色",
        "material": "真皮",
        "brand": "Massimo Dutti",
        "description": "2019秋冬系列真皮运动鞋,原配白色鞋底,Massimo Dutti出品。"
      },
      {
        "id": 2,
        "name": "Polo Ralph Lauren黑色休闲鞋",
        "price": 100,
        "currency": "LEI",
        "image": "https://i.imgur.com/Og3Oh24.jpg",
        "size": 45,
        "colour": "黑色",
        "material": "帆布",
        "brand": "Polo Ralph Lauren",
        "description": "2019春夏系列帆布休闲鞋,原配白色鞋底,Fashion Days出品。"
      },
      {
        "id": 3,
        "name": "Massimo棕色真皮短靴",
        "price": 185,
        "currency": "LEI",
        "image": "https://i.imgur.com/zoqSu87.jpg",
        "size": 45,
        "colour": "棕色",
        "material": "真皮",
        "brand": "Massimo Dutti",
        "description": "2017秋冬系列棕色真皮短靴,Massimo Dutti出品。"
      }
    ],
    "image": "https://i.imgur.com/X0qldXO.jpg",
    "name": "鞋履",
    "description": "靴子、皮鞋、运动鞋,品质保证!"
  },
  "sweaters": {
    "items": [
      {
        "id": 4,
        "name": "修身高领羊绒毛衣",
        "price": 95,
        "currency": "LEI",
        "image": "https://i.imgur.com/6afzuEr.jpg",
        "size": "L",
        "colour": "棕色",
        "material": "羊绒",
        "brand": "DS Damat",
        "description": "2019秋冬系列羊绒修身毛衣,DS Damat出品。"
      },
      {
        "id": 5,
        "name": "拼色毛衣",
        "price": 105,
        "currency": "LEI",
        "image": "https://i.imgur.com/s0VytKc.jpg",
        "size": "L",
        "colour": "拼色",
        "material": "棉",
        "brand": "McNeal",
        "description": "2019秋冬系列拼色毛衣,McNeal出品。"
      },
      {
        "id": 6,
        "name": "保暖格纹羊毛毛衣",
        "price": 165,
        "currency": "LEI",
        "image": "https://i.imgur.com/BWGgzlK.jpg",
        "size": "L",
        "colour": "拼色",
        "material": "羊毛",
        "brand": "SELECTED",
        "description": "2019秋冬系列格纹羊毛保暖毛衣,SELECTED出品。"
      },
      {
        "id": 7,
        "name": "纯羊毛毛衣",
        "price": 160,
        "currency": "LEI",
        "image": "https://i.imgur.com/dTcMbHT.jpg",
        "size": "L",
        "colour": "米白",
        "material": "羊毛",
        "brand": "Massimo Dutti",
        "description": "2019秋冬系列暗纹纯羊毛毛衣,Massimo Dutti出品。"
      }
    ],
    "image": "https://i.imgur.com/SZU7liE.jpg",
    "name": "毛衣",
    "description": "优雅修身毛衣,高端品质。"
  }
}

原有类组件代码(Product.jsx)

import React from "react";
import products from "../utils/products.json";

class Product extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      product: [],
    };
  }

  componentDidMount() {
    const { match } = this.props;
    const productId = match.params.productId;
    const categoryValues = Object.values(products);
    console.log(categoryValues);

    let currentProduct;

    categoryValues.forEach((category) => {
      const findResult = category.items.find((product) => {
        console.log(typeof productId, typeof product.id);
        return product.id === Number(productId);
      });
      if (findResult) {
        currentProduct = findResult;
      }
    });

    this.setState({ product: currentProduct });
  }

  render() {
    const { product } = this.state;

    return (
      <div className="product-page content-min-height container-fluid container-min-max-width">
        <h1 className="my-5 h2">{product.name}</h1>
        <div className="product-info d-flex">
          <div className="image-wrapper d-flex mr-5">
            <img src={product.image} alt="商品展示" />
          </div>
          <div className="product-details">
            <p className="h3 text-danger">
              {product.price} {product.currency}
            </p>
          </div>
        </div>
      </div>
    );
  }
}

export default Product;

问题说明

尝试使用Hooks重构代码时,如果不借助组件生命周期遍历categoryValues,会出现category.items返回undefined的问题。

重构后的Hooks函数组件代码

以下方案兼容React Router v6版本,若使用v5版本可直接从props中获取match参数:

import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; // React Router v6 取路由参数用这个hook
import products from "../utils/products.json";

const Product = () => {
  const [product, setProduct] = useState(null);
  const { productId } = useParams();

  // 等价于原类组件的componentDidMount,依赖项增加productId可以支持参数变化时自动更新商品信息
  useEffect(() => {
    const categoryValues = Object.values(products);
    let currentProduct;
    categoryValues.forEach((category) => {
      const findResult = category.items.find((item) => item.id === Number(productId));
      if (findResult) currentProduct = findResult;
    });
    setProduct(currentProduct);
  }, [productId]);

  // 增加加载态,避免首次渲染时product为null导致读取属性报错
  if (!product) return <div>商品信息加载中...</div>;

  return (
    <div className="product-page content-min-height container-fluid container-min-max-width">
      <h1 className="my-5 h2">{product.name}</h1>
      <div className="product-info d-flex">
        <div className="image-wrapper d-flex mr-5">
          <img src={product.image} alt="商品展示" />
        </div>
        <div className="product-details">
          <p className="h3 text-danger">
            {product.price} {product.currency}
          </p>
          {/* 你可以在这里扩展更多商品属性展示,比如尺码、材质、描述等 */}
        </div>
      </div>
    </div>
  );
};

export default Product;

问题原因说明

之前遇到的undefined问题是因为函数组件首次渲染时,useEffect里的逻辑还没有执行,product还是初始值,如果初始值设为数组,读取product.name自然会返回undefined,增加加载态判断即可解决。useEffect本身就等价于类组件生命周期的组合,这里的用法完全覆盖了原componentDidMount的功能,同时还支持路由参数变化时自动更新商品信息,比原类组件的扩展性更好。

内容的提问来源于stack exchange,提问作者Ionut.d

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 04:06:03