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

React递归渲染任意层级嵌套数据:手风琴UI与分页实现

处理嵌套数据的React通用递归手风琴组件与分页实现

技术需求与问题描述

需要在React中实现通用递归函数,将任意层级的嵌套数据集渲染为可展开/收起的树形手风琴UI。此前使用map的实现不具备通用性,无法适配动态嵌套结构。同时需为laboratory、medications这类包含多对象的节点添加分页功能。

数据集示例

const data = [
  {
    "laboratory": [
      {
        "status": {
          display: "Code",
          value: "23123"
        },
        "observation": [
          {
            display: "Code",
            value: "23123"
          }
        ],
        "resultValue": {
          "quantity": [
            {
              display: "Code",
              value: "23123"
            }
          ],
          "codeableConcept": [
            {
              display: "Code",
              value: "23123"
            },
            {
              display: "Code",
              value: "23123"
            }
          ]
        }
      },
      {
        "status": {
          display: "Code",
          value: "23123"
        },
        "observation": [
          {
            display: "Code",
            value: "23123"
          }
        ],
        "resultValue": {
          "quantity": [
            {
              display: "Code",
              value: "23123"
            }
          ],
          "codeableConcept": [
            {
              display: "Code",
              value: "23123"
            },
            {
              display: "Code",
              value: "23123"
            }
          ]
        }
      }
    ]
  },
  {
    "medications": [
      {
        "status": {
          display: "medications - Code",
          value: "23123"
        },
        "resultValue": {
          "quantity": [
            {
              display: "medications- Code",
              value: "23123"
            },
            {
              display: "medications-Code",
              value: "23123"
            }
          ],
          "codeableConcept": [
            {
              display: "medications-Code",
              value: "23123"
            },
            {
              display: "medications- Code",
              value: "23123"
            }
          ]
        }
      },
      {
        "status": {
          display: "medications - Code",
          value: "23123"
        },
        "resultValue": {
          "quantity": [
            {
              display: "medications- Code",
              value: "23123"
            }
          ],
          "codeableConcept": [
            {
              display: "medications-Code",
              value: "23123"
            }
          ]
        }
      },
      {
        "status": {
          display: "medications - Code",
          value: "23123"
        },
        "resultValue": {
          "quantity": [
            {
              display: "medications- Code",
              value: "23123"
            }
          ],
          "codeableConcept": [
            {
              display: "medications-Code",
              value: "23123"
            }
          ]
        }
      }
    ]
  }
]

期望UI展示(手风琴式树形结构)

-> Laboratory(Main Heading) <h1> Laboratory</h1>
   -> status (Sub-heading) <h3>status</h3>
       Code - 23123 (table data) <table><th>Code</th><tr></tr>23123</table>
   -> observation (Sub-heading)
        Code - 23123 (table data)
   -> ResultValue (Sub-heading)
         -> quantity (Sub-sub heading)
           Code - 23123 (table data)
         -> codeableConcept (Sub-sub heading)
           Code - 23123 (table data)
           Code - 23123 (table data)

-> medications(Main Heading)
   -> status (Sub-heading)
       medications - Code - 23123 (table data)
   -> ResultValue (Sub-heading)
         -> quantity (Sub-sub heading)
           medications- Code - 23123 (table data)
           medications-Code - 23123 (table data)
         -> codeableConcept (Sub-sub heading)
           medications-Code - 23123 (table data)
           medications- Code - 23123 (table data)

现有尝试代码(非通用)

data.map((data) => {
  Object.keys(data).map((header) => {
    //console.log(header)
    data[header].map((headerData) => {
      if(Array.isArray(data[header])) {
          //console.log(headerData)
      }  else {
          console.log(headerData)
      }
    })
  })
})

核心问题

  • 如何编写通用递归代码,处理任意n层级的嵌套数据渲染,避免代码僵化?
  • 如何为laboratory、medications这类包含多个对象的节点实现分页功能?

解决方案

1. 通用递归手风琴组件实现

编写可复用的递归组件,通过判断数据类型(对象、数组、叶子节点)动态渲染结构,用状态管理展开/收起:

import { useState } from 'react';

// 判断是否为叶子节点(包含display和value属性)
const isLeafNode = (item) => {
  return typeof item === 'object' && item !== null && 'display' in item && 'value' in item;
};

const RecursiveAccordion = ({ data, level = 0 }) => {
  const [isExpanded, setIsExpanded] = useState(true);

  // 根据层级匹配标题标签
  const getHeadingTag = (level) => {
    switch(level) {
      case 0: return 'h1';
      case 1: return 'h3';
      case 2: return 'h4';
      default: return 'h5';
    }
  };

  const renderContent = () => {
    if (Array.isArray(data)) {
      return data.map((item, index) => (
        <RecursiveAccordion key={index} data={item} level={level + 1} />
      ));
    } else if (typeof data === 'object' && data !== null) {
      return Object.entries(data).map(([key, value]) => (
        <div key={key} style={{ marginLeft: `${level * 20}px` }}>
          <RecursiveAccordion data={{ [key]: value }} level={level + 1} />
        </div>
      ));
    } else if (isLeafNode(data)) {
      // 渲染叶子节点表格
      return (
        <table border="1" style={{ marginLeft: `${level * 20}px`, marginTop: '8px' }}>
          <thead>
            <tr>
              <th>{data.display}</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>{data.value}</td>
            </tr>
          </tbody>
        </table>
      );
    }
    return null;
  };

  // 获取当前节点标题
  const getTitle = () => {
    if (typeof data === 'object' && data !== null) {
      const keys = Object.keys(data);
      if (keys.length === 1) {
        return keys[0].charAt(0).toUpperCase() + keys[0].slice(1);
      }
    }
    return '';
  };

  const HeadingTag = getHeadingTag(level);

  return (
    <div className="accordion-item">
      {getTitle() && (
        <HeadingTag onClick={() => setIsExpanded(!isExpanded)} style={{ cursor: 'pointer' }}>
          {isExpanded ? '↓' : '→'} {getTitle()}
        </HeadingTag>
      )}
      {isExpanded && renderContent()}
    </div>
  );
};

// 主组件调用
const App = () => {
  return (
    <div className="app">
      {data.map((item, index) => (
        <RecursiveAccordion key={index} data={item} />
      ))}
    </div>
  );
};

export default App;

2. 分页功能实现

针对指定节点封装分页逻辑,在递归组件中判断节点key启用分页:

import { useState } from 'react';

const PagedAccordion = ({ title, items, level }) => {
  const [isExpanded, setIsExpanded] = useState(true);
  const [currentPage, setCurrentPage] = useState(1);
  const itemsPerPage = 2;
  const totalPages = Math.ceil(items.length / itemsPerPage);

  // 计算当前页显示条目
  const currentItems = items.slice(
    (currentPage - 1) * itemsPerPage,
    currentPage * itemsPerPage
  );

  const handlePageChange = (page) => {
    setCurrentPage(page);
  };

  return (
    <div>
      <h1 onClick={() => setIsExpanded(!isExpanded)} style={{ cursor: 'pointer' }}>
        {isExpanded ? '↓' : '→'} {title} (Page {currentPage}/{totalPages})
      </h1>
      {isExpanded && (
        <>
          {currentItems.map((item, index) => (
            <RecursiveAccordion key={index} data={item} level={level + 1} />
          ))}
          <div style={{ marginLeft: `${level * 20}px`, marginTop: '16px' }}>
            {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
              <button
                key={page}
                onClick={() => handlePageChange(page)}
                disabled={page === currentPage}
                style={{ margin: '0 4px', padding: '4px 8px' }}
              >
                {page}
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
};

// 修改递归组件,加入分页判断
const RecursiveAccordion = ({ data, level = 0 }) => {
  const [isExpanded, setIsExpanded] = useState(true);
  const paginatedKeys = ['laboratory', 'medications'];

  // 匹配需要分页的节点
  if (typeof data === 'object' && data !== null) {
    const keys = Object.keys(data);
    if (keys.length === 1 && paginatedKeys.includes(keys[0])) {
      const key = keys[0];
      return (
        <PagedAccordion
          title={key.charAt(0).toUpperCase() + key.slice(1)}
          items={data[key]}
          level={level}
        />
      );
    }
  }

  // 剩余逻辑与原RecursiveAccordion一致...
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:11:11