React实现仅展开点击卡片项而非整行的方法
问题分析
所有卡片点击后同时展开,核心原因是所有卡片共用了同一个展开状态变量,导致单个卡片的状态变更会触发所有卡片的UI更新。
解决方案
方案1:让每个卡片独立维护展开状态(推荐,简单直接)
确保每个CoctailCard组件自行管理自身的展开状态,父组件仅传递鸡尾酒数据,不共享状态。
检查父组件渲染逻辑
父组件循环渲染卡片时,需保证每个卡片有唯一key(用鸡尾酒ID),且不传递共享的展开状态:
// 父组件示例代码 {cocktailsList.map(cocktail => ( <CoctailCard key={cocktail.id} coctails={cocktail} /> ))}
你的CoctailCard代码本身已具备独立维护状态的逻辑,但需注意:
- 必须给循环渲染的卡片添加唯一
key,避免React复用组件导致状态混乱 - 组件内部的
expanded状态是每个卡片实例独有的,点击单个卡片只会改变自身状态
方案2:父组件集中管理所有卡片的展开状态(适合全局控制场景)
如果需要在父组件中统一控制卡片的展开/收起,可通过对象记录每个卡片ID对应的展开状态:
步骤1:修改父组件状态管理
import { useState } from 'react'; // 父组件 const CocktailList = () => { // 用对象存储每个卡片的展开状态,key为卡片ID,value为布尔值 const [expandedCards, setExpandedCards] = useState({}); // 切换指定卡片的展开状态 const handleCardExpand = (cardId) => { setExpandedCards(prev => ({ ...prev, [cardId]: !prev[cardId] })); }; return ( <div className="card-grid"> {cocktailsList.map(cocktail => ( <CoctailCard key={cocktail.id} coctails={cocktail} expanded={!!expandedCards[cocktail.id]} onExpandClick={() => handleCardExpand(cocktail.id)} /> ))} </div> ); };
步骤2:更新CoctailCard组件接收props
import React, {useEffect, useState} from "react"; import {CoctailEntity, GetCoctailRecipe} from "types"; import {Card, CardActions, CardContent, CardHeader, Collapse, IconButton, Typography} from "@mui/material"; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; interface Props { coctails: CoctailEntity; expanded: boolean; onExpandClick: () => void; } export const CoctailCard = (props: Props) => { const [ingredients, setIngradients] = useState<GetCoctailRecipe[] | null>(null) const cocktailIngredients = async () => { const res = await fetch(`http://localhost:3001/cocktail-recipe/${props.coctails.id}`, { headers: {} }) const data = await res.json() setIngradients(data) } // 添加ID依赖,确保卡片切换时重新获取配料 useEffect(() => { cocktailIngredients() }, [props.coctails.id]) if (!ingredients) { return ( <div className="coctailCard"> <p>{props.coctails.name}</p> <p>Brak składników tego koktajlu</p> </div> ) } return ( <Card className='coctailCard' sx={{ backgroundColor: 'antiquewhite', borderRadius: 5, }} > <CardHeader title={props.coctails.name} sx={{padding: '10px 0 0 10px'}} /> <CardActions disableSpacing sx={{padding: '10px 0 0 10px'}} > <IconButton sx={[ { transform: 'rotate(0deg)', transition: (theme) => theme.transitions.create('all', { duration: theme.transitions.duration.shortest, }) }, props.expanded && { transform: 'rotate(180deg)' }, ]} onClick={props.onExpandClick} aria-expanded={props.expanded} aria-label="show more" > <ExpandMoreIcon/> </IconButton> </CardActions> <Collapse in={props.expanded} timeout="auto" unmountOnExit> <CardContent> <Typography paragraph>Skład:</Typography> <Typography paragraph> {ingredients.map(ingradient => <p className='ingradientRow'>{`${ingradient.name} ${ingradient.quantity}ml`}</p>)} </Typography> <Typography paragraph> {props.coctails.description} </Typography> </CardContent> </Collapse> </Card> ) }
额外优化建议
- 给
useEffect添加props.coctails.id依赖,确保卡片ID变化时重新获取对应配料数据 - 循环渲染卡片时必须添加唯一
key属性,避免React组件复用导致状态异常
内容的提问来源于stack exchange,提问作者MatiR
相关产品推荐
相关产品推荐

