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

如何在React中基于嵌套JSON实现无第三方库的树形表格

React 无第三方库实现嵌套树形表格(基于嵌套JSON)

原始嵌套JSON数据

[
  {
    "id": 1,
    "name": "VehicleData",
    "owner": "admin",
    "sub_details": [
      {
        "sub_name": "Lexus",
        "sub_id": 4
      },
      {
        "sub_name": "BMW",
        "sub_id": 3
      }
    ]
  },
  {
    "id": 2,
    "name": "Mobiles",
    "owner": "admin",
    "sub_details": [
      {
        "sub_name": "Apple",
        "sub_id": 2
      }
    ]
  },
  {
    "id": 3,
    "name": "Laptop",
    "owner": "admin",
    "sub_details": []
  }
]

现有基础表格代码

import React, {useState, useEffect} from 'react';
import classes from './TableData.module.css';

const TableData = () => {
    const [oldData, newData] = useState([]);

    useEffect(() => {
        fetchData();
    }, []);

    const Id = (dataId) => {
        alert(dataId);
    };
    const Name = (dataName) => {
        alert(dataName);
    };

    const fetchData = async () => {
        try {
            const response = await fetch('someapi/json');
            const data = await response.json();
            newData(data);
        } catch (e) {
            console.warn(e)
        }
    }

    const DisplayData = oldData.map((data) => {
        return (
            <tr key={data.id}>
                <td>{data.id}</td>
                <td>{data.name}</td>
                <td>{data.owner}</td>
                <td>
                    <button onClick={() => Id(data.id)}>getId</button>
                    <button onClick={() => Name(data.name)}>getname</button>
                </td>
            </tr>
        )
    })

    return (
        <div>
            <div>
                <table>
                    <thead>
                        <tr>
                            <th>id</th>
                            <th>name</th>
                            <th>owner</th>
                        </tr>
                    </thead>
                    <tbody>{DisplayData}</tbody>
                </table>
            </div>
        </div>
    )
}

export default TableData;

需求解决方案

1. 实现嵌套结构并为每行添加按钮

通过递归渲染逻辑处理父行与子行:编写renderRow函数,先渲染父级数据行,再根据父行的sub_details字段渲染子级行,子行通过样式缩进区分层级;同时为父、子行都添加操作按钮。

2. 点击子行按钮同时获取父子数据

渲染子行时,直接将父级数据作为参数传递给按钮的点击回调函数,在回调中即可同时获取父、子两级数据。

3. 实现展开/收起切换

用expandedIds状态存储当前展开的父项ID集合,点击展开/收起按钮时,切换对应ID的状态,控制子行的显示与隐藏。

完整实现代码

import React, { useState, useEffect } from 'react';
import classes from './TableData.module.css';

const TableData = () => {
    const [data, setData] = useState([]);
    // 用Set存储展开的父项ID,方便增删操作
    const [expandedIds, setExpandedIds] = useState(new Set());

    useEffect(() => {
        fetchData();
    }, []);

    const fetchData = async () => {
        try {
            // 替换为你的真实API地址,这里用模拟数据演示
            // const response = await fetch('someapi/json');
            // const resData = await response.json();
            const resData = [
                {
                    "id": 1,
                    "name": "VehicleData",
                    "owner": "admin",
                    "sub_details": [
                        { "sub_name": "Lexus", "sub_id": 4 },
                        { "sub_name": "BMW", "sub_id": 3 }
                    ]
                },
                {
                    "id": 2,
                    "name": "Mobiles",
                    "owner": "admin",
                    "sub_details": [
                        { "sub_name": "Apple", "sub_id": 2 }
                    ]
                },
                {
                    "id": 3,
                    "name": "Laptop",
                    "owner": "admin",
                    "sub_details": []
                }
            ];
            setData(resData);
        } catch (e) {
            console.warn(e);
        }
    };

    // 切换父项的展开/收起状态
    const toggleExpand = (id) => {
        const newExpandedIds = new Set(expandedIds);
        newExpandedIds.has(id) ? newExpandedIds.delete(id) : newExpandedIds.add(id);
        setExpandedIds(newExpandedIds);
    };

    // 父行按钮点击回调:获取父级数据
    const handleParentClick = (parent) => {
        alert(`父ID: ${parent.id}, 父名称: ${parent.name}`);
    };

    // 子行按钮点击回调:同时获取父级与子级数据
    const handleChildClick = (parent, child) => {
        alert(`父ID: ${parent.id}, 父名称: ${parent.name}\n子ID: ${child.sub_id}, 子名称: ${child.sub_name}`);
    };

    // 递归渲染父行与子行
    const renderRow = (parentItem) => {
        const isExpanded = expandedIds.has(parentItem.id);
        return (
            <>
                {/* 父级行 */}
                <tr key={parentItem.id} className={classes.parentRow}>
                    <td>{parentItem.id}</td>
                    <td>
                        {/* 仅当有子项时显示展开/收起按钮 */}
                        {parentItem.sub_details.length > 0 && (
                            <button onClick={() => toggleExpand(parentItem.id)} className={classes.toggleBtn}>
                                {isExpanded ? '▼' : '▶'}
                            </button>
                        )}
                        {parentItem.name}
                    </td>
                    <td>{parentItem.owner}</td>
                    <td>
                        <button onClick={() => handleParentClick(parentItem)}>获取父数据</button>
                    </td>
                </tr>
                {/* 子级行:仅展开状态下渲染 */}
                {isExpanded && parentItem.sub_details.map(child => (
                    <tr key={child.sub_id} className={classes.childRow}>
                        <td>{child.sub_id}</td>
                        <td style={{ paddingLeft: '24px' }}>{child.sub_name}</td>
                        <td>{parentItem.owner}</td>
                        <td>
                            <button onClick={() => handleChildClick(parentItem, child)}>获取父子数据</button>
                        </td>
                    </tr>
                ))}
            </>
        );
    };

    return (
        <div className={classes.tableContainer}>
            <table className={classes.table}>
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>名称</th>
                        <th>所有者</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {data.map(item => renderRow(item))}
                </tbody>
            </table>
        </div>
    );
};

export default TableData;

配套样式参考(TableData.module.css)

.tableContainer {
    margin: 20px;
}

.table {
    border-collapse: collapse;
    width: 100%;
    border: 1px solid #ddd;
}

.table th, .table td {
    border: 1px solid #ddd;
    padding: 8px;
    text-align: left;
}

.table th {
    background-color: #f2f2f2;
}

.parentRow {
    background-color: #f9f9f9;
}

.childRow {
    background-color: #ffffff;
}

.toggleBtn {
    margin-right: 8px;
    border: none;
    background: transparent;
    cursor: pointer;
}

关键逻辑说明

  • 嵌套渲染:通过renderRow函数递归处理父项与子项,子行通过padding-left实现视觉层级缩进。
  • 展开/收起控制:用Set存储展开的父ID,点击按钮时切换对应ID的状态,动态控制子行的渲染。
  • 父子数据传递:渲染子行时直接将父项数据传入点击回调,实现一次获取两级数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:55:19