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

React使用map()渲染食谱数组时如何仅切换单个食谱的显示隐藏状态

问题原因

你当前所有食谱共用了同一个全局的布尔状态,所以任意按钮触发状态修改,所有食谱的展示状态都会同步切换,这是问题的核心。

解决方案

推荐两种实现方式,你可以根据需求选择:

方案1:拆分独立子组件(更符合React组件化思想,推荐)

把单个食谱项封装成独立组件,每个组件自己维护自身的展开/收起状态,组件之间状态完全隔离,不会互相影响。
修改后的完整代码如下:

import React, { useState } from 'react';

const recipes = [
  {
    _id: 1,
    title: "Spaghetti",
    type: "Dinner or Lunch",
    category: "vegan",
    image: "http://via.placeholder.com/350x250",
    cookingTime: 35,
    ingredients: [[1, "onion"], [3, "tomato"], [1, "aubergine"], [1, "beans"], [1, "tomatoesauce"], [1, "tomatoconcentrate"], [1, "pepper"], [1, "salt"], [1, "pasta"]],
    cookingSteps: [["Boil a pot full of watter. Once the watter is boiling, add the pasta. The cooking time depends on the pasta."], ["Cut the onion and the garlic. Add them to a poth with olive oil. Cook for 2 to 3 minutes"], ["Cut the other vegetables. After the onion and the garlic are marrinated, add the different vegtables to the poth. Let them cook with the lid on for about 10 to 15 minutes or untill the vegetables are ready."], ["Once the vegetables are ready, add the tomato sauce and the tomato concentrate. Add the beans. Let this cook for another five minutes"], ["Remove the lid from the pot. Add pepper and salt and other additional spcices, like italian spcies or special peppers"]]
  },
  {
    _id: 2,
    title: "Eggs",
    type: "Breakfast",
    category: "vegan",
    image: "http://via.placeholder.com/350x250",
    cookingTime: 10,
    ingredients: [[4, "egg"], [1, "tomato"], [1, "pepper"], [1, "salt"]],
    cookingSteps: [["cut the tomato. Heat olive oil in a pan, add the tomato and bake it till the outer peal of the tomato gets soft. If you like pepper, it is adviced to first bake the pepper a bit in the pan"], ["Meanwhile, scrambel the eggs. Once the tomatoes are ready (see step 1), add the eggs"]]
  }
];

// 独立的食谱项组件,自己管理展开状态
function RecipeItem({ recipe }) {
  // 修正原来的状态命名,更清晰:showDetail控制是否展示详情,setShowDetail是修改状态的方法
  const [showDetail, setShowDetail] = useState(false);

  return (
    <div className="col-md-3">
      <div className="card bwm-card">
        <div className="card-title">
          <h2>{recipe.title}</h2>
        </div>
        <img className="card-img-top" src={recipe.image} alt={recipe.title} />
        <div className="card-subtitle">
          <h3><b>Type: {recipe.type} </b></h3>
          <h3><b>Category: {recipe.category}</b></h3>
          <h3><b>Cooking time: {recipe.cookingTime} minutes</b></h3>
        </div>
        <div>
          {showDetail ? 
            <div className="card-body">
              <div className="card-body-ingredient">
                {/* 优化:table只渲染一次,循环渲染行即可,避免生成多个表格 */}
                <table>
                  <thead>
                    <tr>
                      <th>Quantity</th>
                      <th>Ingredient</th>
                    </tr>
                  </thead>
                  <tbody>
                    {recipe.ingredients.map((ingredient, i) => (
                      <tr key={i}>
                        <td>{ingredient[0]}</td>
                        <td>{ingredient[1]}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
              <div className="card-body-cookingsteps">
                {recipe.cookingSteps.map((cookingstep, i) => (
                  <p key={i}>{cookingstep}</p>
                ))}
              </div>
              <div>
                <button type="button" onClick={() => setShowDetail(false)}>Hide full recipe</button>
              </div>
            </div>
          : 
            <button type="button" onClick={() => setShowDetail(true)}>Show full recipe</button>
          } 
        </div>
      </div>
    </div>
  )
}

function DisplayRecipes(){
  return(
    <div className="card-list">
      <div className="container">
        <h1 className="page-title">Our vegan recipes...</h1>
        <div className="row">
          {recipes.map(recipe => (
            <RecipeItem key={recipe._id} recipe={recipe} />
          ))} 
        </div>
      </div>
    </div>
  )
} 

export default DisplayRecipes;

方案2:父组件存储展开ID(无需拆组件,适合少量代码修改)

如果你不想拆分组件,可以在父组件存储当前展开的食谱ID,点击时传入对应ID做匹配即可,核心修改点如下:

function DisplayRecipes(){
  // 存储当前展开的食谱ID,null代表都不展开
  const [activeRecipeId, setActiveRecipeId] = useState(null);

  return(
    <div className="card-list">
      <div className="container">
        <h1 className="page-title">Our vegan recipes...</h1>
        <div className="row">
          {
            recipes.map((recipe)=>{
              // 判断当前食谱是否是展开状态
              const isActive = activeRecipeId === recipe._id;
              return(
                <div key={recipe._id} className="col-md-3">
                  {/* 其余原有代码不变,仅修改判断条件和按钮点击事件 */}
                  <div>
                    { isActive ? 
                      // 详情部分省略原有代码,仅需修改按钮点击
                      <button type="button" onClick={() => setActiveRecipeId(null)}>Hide full recipe</button>
                    : 
                      <button type="button" onClick={() => setActiveRecipeId(recipe._id)}>Show full recipe</button>
                    } 
                  </div>
                </div>
              )
            })
          } 
        </div>
      </div>
    </div>
  )
}
额外优化提示
  1. 你原来的useState命名有歧义,建议遵循[状态名, set+状态名]的通用命名规范,避免逻辑混淆
  2. 原代码中每个配料都循环生成完整table,会导致页面生成多个表格结构,建议把table提到循环外,仅循环渲染表格行即可,和方案1中的优化一致
  3. 按钮类型不需要用submit,改用button即可,避免放在表单里触发默认提交行为

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 01:39:03