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'
现咨询以下问题:
- 该单组件复用方案是否可行?
- 是否需为三类数据分别创建卡片组件?
- 大型React应用中卡片组件的最优处理方式?
- 当前方案是否合理,有何改进建议?
问题解答
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判断,新增类型时需要修改多处逻辑。
改进建议
- 修正函数调用:把
CardDetails[0]改为CardDetails[0](),确保拿到函数返回值。 - 用枚举约束卡片类型:定义
CardType枚举,替换cardtype的字符串类型,避免无效值。 - 替换switch为配置表:用上面提到的配置化方式,减少重复代码,提升扩展性。
- 优化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; - 修复代码错误:删除重复的
Book导入、修正变量名、导入Item类型。
内容的提问来源于stack exchange,提问作者Himanshu Sharma
相关产品推荐
相关产品推荐

