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

ReactJS:如何用单个卡片组件渲染不同列表的多源数据?

问题描述

我从item.json、toys.json、books.json等多个源获取JSON数据,希望复用单个AppCard组件,渲染不同父组件中映射的items、toys、books的对应属性(如item.name、toy.category等)。

当前编写的AppCard组件代码如下:

import { Card, CardHeader, Typography, CardMedia } from "@mui/material";
import { Fragment } from "react";
import AppAvatar from "./AppAvatar";
import { Toy } from "../../models/Toys";
import { Book } from "../../models/Books";
import { Book } from "../../models/Books"; // 重复导入

interface Props {
  toy?: Toy; // 来自映射toys的父组件
  item?: Item; // 来自映射items的父组件
  book?: Book; // 来自映射books的父组件

  cardtype: string; // 用来判断卡片要渲染的数据源
}

export default function AppCard({ item, toy, book, cardtype }: Props) {
  // 我创建了一个函数数组,打算根据索引调用对应的函数
  const CardDetails = [
    function setPictureUrl() {
      switch (cardtype) {
        case 'toyCard': return 'ImageA.jpg';
        case 'bookCard': return 'ImageB.jpg';
        case 'itemCard': return 'ImageC.jpg';
        default: return '';
      }
    },
    function setName() {
      switch (cardtype) {
        case 'toyCard': return toy?.toyName;
        case 'bookCard': return book?.author;
        case 'itemCard': return item?.itemName;
        default: return '';
      }
    },
    function setPrice() {
      switch (cardtype) {
        case 'toyCard': return toy?.price;
        case 'bookCard': return books?.price; // 变量名错误:应为book而非books
        case 'itemCard': return item?.price;
        default: return '';
      }
    },
    function setRating() {
      switch (cardtype) {
        case 'toyCard': return toy?.toyRating;
        case 'bookCard': return book?.bookRating;
        case 'itemCard': return item?.itemRating;
        default: return '';
      }
    },
  ];

  return (
    <Fragment>
      <Card>
        <CardHeader
          avatar={
            <AppAvatar
              src={CardDetails[0]} // 此处报错
              alt={CardDetails[1]} /> // 此处报错
          }
          title={CardDetails[1]} // 此处报错
        />
        <CardMedia
          component={'img'}
          height={'260'}
          image={CardDetails[0]} // 此处报错
          alt={CardDetails[1]} // 此处报错,且多了一个多余的点
        />
      </Card>
    </Fragment>
  )
}

运行时出现TypeScript错误:

Type '(() => string | undefined) | (() => number | "" | undefined)' is not assignable to type 'string | undefined'.
  Type '() => string | undefined' is not assignable to type 'string'.ts(2322)
Avatar.d.ts(16, 5): The expected type comes from property 'alt' which is declared here on type 'IntrinsicAttributes & Props'

现咨询以下问题:

  1. 该单组件复用方案是否可行?
  2. 是否需为三类数据分别创建卡片组件?
  3. 大型React应用中卡片组件的最优处理方式?
  4. 当前方案是否合理,有何改进建议?

问题解答

1. 单组件复用方案完全可行

你的核心思路没问题,当前报错只是因为代码里的函数调用方式错误:CardDetails数组存的是函数,直接取CardDetails[0]拿到的是函数本身,而非执行后的返回值,导致TypeScript类型不匹配。只要改成CardDetails[0]()这种调用方式,方案就能正常运行。

2. 不需要单独创建三类卡片组件

除非三类卡片的UI结构差异极大(比如有的要加专属操作按钮、有的要展示完全不同的布局模块),否则完全没必要拆分。复用同一个组件能减少重复代码,后续统一修改样式或逻辑时也更高效。

3. 大型React应用中卡片组件的最优处理方式

大型项目里通常采用分层抽象+配置化的思路:

  • 基础卡片组件:封装最通用的UI结构(比如MUI Card的基础样式、布局框架),不绑定任何业务数据,只接收通用的props(如title、imageUrl、price等)。
  • 业务适配层:针对不同业务类型(玩具、书籍、物品),做数据到通用props的映射,或者通过配置表统一管理映射规则。
  • 配置化映射:定义一个字段映射表,把不同数据类型的属性对应到卡片需要的通用字段,避免大量重复的switch判断,后续新增类型时只需添加配置项即可。

举个配置化的示例:

// 定义卡片通用字段的配置类型
interface CardConfig<T> {
  getImage: (data: T) => string;
  getTitle: (data: T) => string;
  getPrice: (data: T) => number | string;
  getRating: (data: T) => number | string;
}

// 枚举卡片类型,避免字符串传错
enum CardType {
  Toy = 'toyCard',
  Book = 'bookCard',
  Item = 'itemCard'
}

// 不同类型的映射配置表
const cardConfigs: Record<CardType, CardConfig<any>> = {
  [CardType.Toy]: {
    getImage: () => 'ImageA.jpg',
    getTitle: (toy: Toy) => toy.toyName || '',
    getPrice: (toy: Toy) => toy.price || '',
    getRating: (toy: Toy) => toy.toyRating || ''
  },
  [CardType.Book]: {
    getImage: () => 'ImageB.jpg',
    getTitle: (book: Book) => book.author || '',
    getPrice: (book: Book) => book.price || '',
    getRating: (book: Book) => book.bookRating || ''
  },
  [CardType.Item]: {
    getImage: () => 'ImageC.jpg',
    getTitle: (item: Item) => item.itemName || '',
    getPrice: (item: Item) => item.price || '',
    getRating: (item: Item) => item.itemRating || ''
  }
};

4. 当前方案的问题与改进建议

现有问题

  • 函数调用错误:直接引用函数而非执行函数,导致类型不匹配。
  • 代码错误:重复导入Book类型、setPrice里的变量名写错(books应为book)、未导入Item类型。
  • 类型不安全:cardtype用字符串类型,没有约束,容易传错值;多个可选props共存,可能出现多数据源同时传入的混乱情况。
  • 代码冗余:大量switch判断,新增类型时需要修改多处逻辑。

改进建议

  1. 修正函数调用:把CardDetails[0]改为CardDetails[0](),确保拿到函数返回值。
  2. 用枚举约束卡片类型:定义CardType枚举,替换cardtype的字符串类型,避免无效值。
  3. 替换switch为配置表:用上面提到的配置化方式,减少重复代码,提升扩展性。
  4. 优化Props类型:用联合类型确保同一时间只有一个数据源被传入,避免逻辑混乱:
    interface ToyCardProps {
      type: CardType.Toy;
      data: Toy;
    }
    interface BookCardProps {
      type: CardType.Book;
      data: Book;
    }
    interface ItemCardProps {
      type: CardType.Item;
      data: Item;
    }
    type Props = ToyCardProps | BookCardProps | ItemCardProps;
    
  5. 修复代码错误:删除重复的Book导入、修正变量名、导入Item类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:20:28