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

React从API获取JSON数据赋值常量渲染树形组件的实现方法

React 树形组件异步加载API数据实现方案

问题场景

开发React应用构建文件夹树形结构时,将JSON数据硬编码为treeData常量时功能运行正常,对应代码如下:

Treelist Page 代码

const treeData = [{here is my json}];

const TreeList = () => {
  return (
    <>
      <Header title="Tree Data Visualization" />
      <ExternalInfo page="treeList" />
      <div className="row">
        <div className="col text-center">
            <div className="row mt-3 d-flex justify-content-center">
              <div className="col-lg-8 text-left text-dark">
                  <Tree data = {treeData}/>
              </div>
            </div>
        </div>
      </div>
    </>
  );
};

export default TreeList;

Treelist Component 代码

const Tree = ({ data = [] }) => {
  return (
    <div className="d-tree">
      <ul className="d-flex d-tree-container flex-column">
        {data.map((tree) => (
          <TreeNode node={tree} />
        ))}
      </ul>
    </div>
  );
};

const TreeNode = ({ node }) => {
  const [childVisible, setChildVisiblity] = useState(false);
  const hasChild = node.elements ? true : false;
  const hasChildelements = node.elements ? true : false;
  const hasNodeDescription = node.description ? true : false;

  return (
    <li className="d-tree-node border-0">
      <div className="d-flex" onClick={(e) => setChildVisiblity((v) => !v)}>
        {hasChild && (
          <div
            className={`d-inline d-tree-toggler ${
              childVisible ? "active" : ""
            }`}
          >
            <FontAwesomeIcon icon="caret-right" />
          </div>
        )}
    </li>
  );
};

export default Tree;

当改为从API接口获取JSON数据赋值时功能出现异常,测试用API地址为https://someapi.free.beeceptor.com/。

故障核心原因

  • API请求是异步行为,首次组件渲染时数据还未返回,未设置初始空值会导致data.map执行报错
  • 多数接口返回的响应会有外层包裹(比如{code: 200, data: [/*树节点数组*/]}结构),直接将整个响应对象传入组件,和硬编码时的纯数组结构不匹配
  • 原有TreeNode组件缺失子节点递归渲染逻辑,即使数据正确返回也无法展示多层级结构
  • 未处理加载、请求错误的边界场景,异常发生时无兜底提示

正确实现代码

1. 修正TreeList页面,增加异步数据拉取逻辑

import { useState, useEffect } from 'react';
import Header from './Header';
import ExternalInfo from './ExternalInfo';
import Tree from './Tree';

const TreeList = () => {
  // 初始值设为空数组,避免首次渲染map报错
  const [treeData, setTreeData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  useEffect(() => {
    const fetchTreeData = async () => {
      setLoading(true);
      setError('');
      try {
        const res = await fetch('https://someapi.free.beeceptor.com/');
        if (!res.ok) throw new Error('请求失败');
        const result = await res.json();
        // 根据接口实际返回结构取树数组,以下为兼容写法
        setTreeData(Array.isArray(result) ? result : result.data || []);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchTreeData();
  }, []);

  return (
    <>
      <Header title="Tree Data Visualization" />
      <ExternalInfo page="treeList" />
      <div className="row">
        <div className="col text-center">
            <div className="row mt-3 d-flex justify-content-center">
              <div className="col-lg-8 text-left text-dark">
                {loading && <div>数据加载中...</div>}
                {error && <div className="text-danger">加载失败:{error}</div>}
                {!loading && !error && <Tree data={treeData}/>}
              </div>
            </div>
        </div>
      </div>
    </>
  );
};

export default TreeList;

2. 补全Tree组件的递归渲染逻辑

import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCaretRight } from '@fortawesome/free-solid-svg-icons';

const Tree = ({ data = [] }) => {
  return (
    <div className="d-tree">
      <ul className="d-flex d-tree-container flex-column list-unstyled pl-0">
        {data.map((tree, idx) => (
          <TreeNode key={tree.id || idx} node={tree} />
        ))}
      </ul>
    </div>
  );
};

const TreeNode = ({ node }) => {
  const [childVisible, setChildVisiblity] = useState(false);
  const hasChild = Array.isArray(node.elements) && node.elements.length > 0;
  const hasNodeDescription = !!node.description;

  return (
    <li className="d-tree-node border-0 pl-3">
      <div className="d-flex align-items-center" onClick={() => setChildVisiblity(v => !v)} style={{cursor: hasChild ? 'pointer' : 'default'}}>
        {hasChild && (
          <div
            className={`d-inline d-tree-toggler mr-1 transition-transform ${
              childVisible ? "active" : ""
            }`}
            style={{transform: childVisible ? 'rotate(90deg)' : 'rotate(0)'}}
          >
            <FontAwesomeIcon icon={faCaretRight} />
          </div>
        )}
        {/* 节点名称,根据实际接口字段调整 */}
        <span className="mr-2">{node.name || node.title}</span>
        {hasNodeDescription && <span className="text-muted small">{node.description}</span>}
      </div>
      {/* 递归渲染子节点 */}
      {hasChild && childVisible && (
        <ul className="list-unstyled pl-3">
          {node.elements.map((childNode, idx) => (
            <TreeNode key={childNode.id || idx} node={childNode} />
          ))}
        </ul>
      )}
    </li>
  );
};

export default Tree;

验证要点

  • 拿到接口响应后先在控制台打印,确认传入Tree组件的是纯节点数组,结构和之前硬编码的treeData完全一致
  • 确认每个节点的子节点字段名和代码中匹配(示例用的是elements字段,如果接口用children等其他字段要同步修改)
  • 列表渲染记得加唯一key,避免渲染异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:54:24