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

如何在ReactJS中获取层级JSON各级键并实现分类详情展示

React实现餐厅分类卡片及菜品详情展示

核心思路

  1. 用Object.entries()将层级JSON对象转换为可遍历数组,解决嵌套对象的渲染问题
  2. 通过React状态useState控制当前选中的分类,实现点击切换详情的交互
  3. 分区域渲染:先展示所有分类卡片,选中后渲染对应分类下的菜品详情

完整实现代码

import { useState, useEffect } from 'react';

const RestaurantMenu = () => {
  // 状态存储API数据、当前选中的分类
  const [restaurantData, setRestaurantData] = useState(null);
  const [selectedCategory, setSelectedCategory] = useState(null);

  // 模拟API请求获取数据(实际项目替换为fetch/axios)
  useEffect(() => {
    const mockApiData = { 
      "Categories": {
              "Breakfast": {
                "Poha": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "123",
                  "Image": "image url",
                  "Price": 30
                },
                "Upma": {
                  "Description": "Chunks of paneer, a type of fresh cheese, are marinated in spices and are then arranged on a stick with capsicums (bell peppers), onions and tomatoes. These sticks are grilled in a tandoor and the dish is thereafter served hot, seasoned with lemon juice and chaat masala.[8] It is sometimes accompanied by salad or mint chutney. Tikka dishes traditionally go well with mint chutney. The paneer, though tender, has a crisp singe on the surface.",
                  "Likes": "35",
                  "Image": "image url",
                  "Price": 35
                }
              },
              "Starters": {
                "Chicken Bites": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "55",
                  "Image": "image url",
                  "Price": 90
                },
                "Gobi Tikka": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "9",
                  "Image": "image url",
                  "Price": 50
                },
                "Mashroom Tikka ": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "87",
                  "Image": "image url",
                  "Price": 60
                }
              },
              "Chinese": {
                "Chicken Hakka Noodles": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "525",
                  "Image": "image url",
                  "Price": 150
                },
                "Veg Schezwan Noodles ": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "39",
                  "Image": "image url",
                  "Price": 140
                },
                "Veg Hakka Noodles": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "90",
                  "Image": "image url",
                  "Price": 130
                }
              },
              "Main Course": {
                "Chicken Masala": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "13",
                  "Image": "image url",
                  "Price": 340
                },
                "Paneer Tikka": {
                  "Description": "Chunks of paneer, a type of fresh cheese, are marinated in spices and are then arranged on a stick with capsicums (bell peppers), onions and tomatoes. These sticks are grilled in a tandoor and the dish is thereafter served hot, seasoned with lemon juice and chaat masala.[8] It is sometimes accompanied by salad or mint chutney. Tikka dishes traditionally go well with mint chutney. The paneer, though tender, has a crisp singe on the surface.",
                  "Likes": "34",
                  "Image": "image url",
                  "Price": 250
                },
                "Paneer Tikka Masala": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "10",
                  "Image": "image url",
                  "Price": 260
                }
              },
              "Ice-creams": {
                "Chocolate": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "12",
                  "Image": "image url",
                  "Price": 100
                },
                "Vanilla": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "11",
                  "Image": "image url",
                  "Price": 100
                },
                "Creamy Milk": {
                  "Description": "This is item description given by restaurant owner",
                  "Likes": "13",
                  "Image": "image url",
                  "Price": 100
                }
              }   },   
    "Restaurant Details": {
              "Insta Profile link": "https://instaprofileurl.com",
              "Owner Contact": "+91 5007 227 997",
              "Restaurant Address": "Restaurant address",
              "Owner Name": "Mr. Owner Surname",
              "Restaurant website": "https://www.myrestaurant.com",
              "Owner Email ": "owner@gmail.com",
              "Facebook Profile link": "https://facebookprofileurl.com"   
           },   
    "rid": "unique_restaurant_name" 
    };
    setRestaurantData(mockApiData);
  }, []);

  if (!restaurantData) return <div>Loading...</div>;

  // 将Categories对象转为[分类名, 菜品列表]的数组,方便遍历
  const categories = Object.entries(restaurantData.Categories);

  return (
    <div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
      {/* 分类卡片区域 */}
      <div style={{ display: 'flex', gap: '15px', flexWrap: 'wrap', marginBottom: '30px' }}>
        {categories.map(([categoryName]) => (
          <div
            key={categoryName}
            onClick={() => setSelectedCategory(categoryName === selectedCategory ? null : categoryName)}
            style={{
              padding: '15px 25px',
              border: '1px solid #ddd',
              borderRadius: '8px',
              cursor: 'pointer',
              backgroundColor: selectedCategory === categoryName ? '#f0f0f0' : 'white',
              transition: 'background-color 0.2s'
            }}
          >
            <h3>{categoryName}</h3>
          </div>
        ))}
      </div>

      {/* 菜品详情区域 */}
      {selectedCategory && (
        <div>
          <h2>{selectedCategory}</h2>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '20px' }}>
            {Object.entries(restaurantData.Categories[selectedCategory]).map(([itemName, itemDetails]) => (
              <div key={itemName} style={{ border: '1px solid #ddd', borderRadius: '8px', padding: '15px' }}>
                <img 
                  src={itemDetails.Image} 
                  alt={itemName} 
                  style={{ width: '100%', height: '180px', objectFit: 'cover', borderRadius: '4px', marginBottom: '10px' }}
                />
                <h3>{itemName}</h3>
                <p>₹{itemDetails.Price}</p>
                <p>Likes: {itemDetails.Likes}</p>
                <p style={{ fontSize: '14px', color: '#666', marginTop: '10px' }}>{itemDetails.Description}</p>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

export default RestaurantMenu;

关键逻辑说明

  1. 数据遍历:Object.entries()把嵌套对象转为键值对数组,比如Object.entries(restaurantData.Categories)会得到[["Breakfast", {Poha: ...}], ...],直接用map就能渲染分类。
  2. 状态切换:点击分类卡片时,切换selectedCategory的值——再次点击同一分类会清空状态,隐藏详情。
  3. 条件渲染:只有当selectedCategory有值时,才渲染对应的菜品详情区域。
  4. 基础样式:用内联样式实现简单布局,实际项目可替换为CSS模块、Tailwind等方案。

扩展优化方向

  • 添加返回按钮,快速回到分类列表
  • 替换加载状态为骨架屏,提升用户体验
  • 对过长的菜品描述做截断处理,添加展开/收起功能
  • 集成真实API时,增加错误捕获和异常提示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 18:17:56