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

API返回单条记录时React组件无法正常渲染的问题排查

问题分析与解决方案

核心错误点

  1. 状态类型不匹配
    useFetch中products初始值是数组[],但获取单个产品时你将其设置为对象setProducts({ ...response.data }),导致:

    • 类型冲突(TypeScript严格模式下会直接报错)
    • CardDetail中products[0].title抛出Cannot read properties of undefined错误,因为此时products是对象而非数组
  2. 未处理异步加载边界
    组件渲染时数据尚未返回,直接访问products[0].title必然报错,完全没处理加载中、数据为空的状态。

  3. hasMore逻辑错误
    单个产品接口返回的是单条对象,不存在total字段,if (products.length < response.data.total)会抛出Cannot read property 'total' of undefined,且hasMore对单个请求无意义。

  4. id参数处理不严谨
    useParams()返回的id可能为undefined,直接Number.parseInt(id)会得到NaN,导致无效请求。


修复方案:重构useFetch(推荐)

将列表和单个请求的逻辑拆分,用类型区分返回结果,覆盖所有边界状态:

import axios, { Canceler } from 'axios';
import { useEffect, useState } from 'react';
import { dummyProductType } from '../types/types';

// 定义返回类型,明确区分列表和单个数据
type UseFetchResult = 
  | { type: 'list', data: dummyProductType[], isLoading: boolean, error: boolean, hasMore: boolean }
  | { type: 'single', data: dummyProductType | null, isLoading: boolean, error: boolean };

export const useFetch = ({ limit, id }: { limit?: number, id?: number }) => {
  const [data, setData] = useState<dummyProductType[] | dummyProductType | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [hasMore, setHasMore] = useState(false);
  const [error, setError] = useState(false);

  useEffect(() => {
    let cancel: Canceler;
    let isListRequest = false;

    const config = id == null ? (isListRequest = true, {
      method: 'GET',
      url: 'https://dummyjson.com/products/',
      params: { limit },
      cancelToken: new axios.CancelToken(c => cancel = c),
    }) : {
      method: 'GET',
      url: `https://dummyjson.com/products/${id}`,
      cancelToken: new axios.CancelToken(c => cancel = c),
    };

    async function fetchData() {
      setIsLoading(true);
      setError(false);
      try {
        const response = await axios(config);
        if (isListRequest) {
          // 列表请求:追加数据适配无限滚动
          setData(prev => [...(prev as dummyProductType[]), ...response.data.products]);
          // 判断是否还有更多数据
          setHasMore(
            response.data.products.length > 0 && 
            ((prev as dummyProductType[]).length + response.data.products.length) < response.data.total
          );
        } else {
          // 单个请求:直接设置单条数据
          setData(response.data);
          setHasMore(false);
        }
      } catch (err) {
        if (!axios.isCancel(err)) setError(true);
      } finally {
        setIsLoading(false);
      }
    }

    fetchData();
    return () => cancel();
  }, [limit, id]);

  // 根据请求类型返回对应结构
  if (id != null) {
    return {
      type: 'single' as const,
      data: data as dummyProductType | null,
      isLoading,
      error,
    };
  } else {
    return {
      type: 'list' as const,
      data: (data as dummyProductType[]) || [],
      isLoading,
      error,
      hasMore,
    };
  }
};

修复CardDetail组件

处理加载、错误、数据为空的边界状态:

import { Card, CardHeader, CardMedia, CardContent } from '@mui/material';
import { useParams } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';

export const CardDetail = () => {
  const { id } = useParams();
  // 处理id为undefined或非数字的情况
  const productId = id ? Number.parseInt(id) : null;
  const { type, data, isLoading, error } = useFetch({ id: productId });

  // 加载中状态
  if (isLoading) return <div>加载中...</div>;

  // 错误或数据为空状态
  if (error || !data) return <div>产品不存在或加载失败</div>;

  // 渲染单个产品数据
  return (
    <Card key={productId}>
      <CardHeader title={data.title} />
      <CardMedia
        component='img'
        image={data.thumbnail}
        sx={{ height: '150px' }}
      />
      <CardContent>
        <p>{data.description}</p>
        <p>价格: ${data.price}</p>
      </CardContent>
    </Card>
  );
};

最小修复方案(不重构useFetch)

如果不想大规模修改,可做以下调整:

  1. 在useFetch的单个请求中,将数据包装为数组:
// 替换原单个请求的setProducts逻辑
setProducts([response.data]);
setHasMore(false);
  1. 在CardDetail中添加状态判断:
export const CardDetail = () => {
  const { id } = useParams();
  const productId = id ? Number.parseInt(id) : null;
  const { products, isLoading, error } = useFetch({ id: productId });

  if (isLoading) return <div>加载中...</div>;
  if (error || products.length === 0) return <div>产品不存在</div>;

  const product = products[0];
  return (
    <Card key={id}>
      <CardHeader title={product.title}></CardHeader>
      <CardMedia
        component='img'
        image={product.thumbnail}
        sx={{ height: '150px' }}></CardMedia>
    </Card>
  );
};

内容的提问来源于stack exchange,提问作者f4lk0N

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 22:00:58