Apifetch

import { useEffect, useState } from "react";
import "./styles.css";
const mockData = [
  {
    id: 1,
    name: "README.md",
  },
  {
    id: 2,
    name: "Documents",
    children: [
      {
        id: 3,
        name: "Word.doc",
      },
      {
        id: 4,
        name: "Powerpoint.ppt",
      },
    ],
  },
  {
    id: 5,
    name: "Downloads",
    children: [
      {
        id: 6,
        name: "unnamed.txt",
      },
      {
        id: 7,
        name: "Misc",
        children: [
          {
            id: 8,
            name: "foo.txt",
          },
          {
            id: 9,
            name: "bar.txt",
          },
        ],
      },
    ],
  },
];
// --- 1. 模拟一个异步 API ---
// 这是一个更真实的 fetchData,它返回一个 Promise,并模拟了网络延迟和可能的失败。
function fetchData() {
  console.log("Fetching data...");
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 模拟 5% 的概率请求失败
      if (Math.random() > 0.05) {
        // 深拷贝一份数据,避免后续操作(如排序)意外修改原始数据
        const dataCopy = JSON.parse(JSON.stringify(mockData));
        resolve(dataCopy);
      } else {
        reject(new Error("Failed to fetch data from the server."));
      }
    }, 1000); // 模拟 1 秒的网络延迟
  });
}

// --- 2. 创建一个可递归的、负责渲染文件/文件夹的组件 ---
// 这是展示树状结构的最佳实践。
function Entry({ entry, depth }) {
  const [isExpanded, setIsExpanded] = useState(true); // 默认展开

  // 根据是否有 children 判断是文件夹还是文件
  const isDirectory = entry.children && entry.children.length > 0;

  const handleToggle = () => {
    setIsExpanded(!isExpanded);
  };

  // 文件夹排前面,文件排后面
  const sortedChildren = isDirectory
    ? [...entry.children].sort((a, b) => {
        const aIsDir = !!a.children;
        const bIsDir = !!b.children;
        return bIsDir - aIsDir; // true (1) - false (0) = 1, b会排前面
      })
    : null;

  return (
    <div>
      <div
        style={{ paddingLeft: `${depth * 20}px` }}
        className="entry-item"
        onClick={isDirectory ? handleToggle : undefined}
      >
        {isDirectory && <span>{isExpanded ? "📂" : "📁"} </span>}
        {!isDirectory && <span>📄 </span>}
        {entry.name}
      </div>
      {/* 递归渲染子节点 */}
      {isExpanded && isDirectory && (
        <div>
          {sortedChildren.map((child) => (
            <Entry key={child.id} entry={child} depth={depth + 1} />
          ))}
        </div>
      )}
    </div>
  );
}

// --- 3. 主应用组件 App ---
export default function App() {
  const [result, setResult] = useState([]);
  const [loading, setLoading] = useState(true); // 初始状态即为 loading
  const [error, setError] = useState(null);

  // --- 正确处理异步副作用 ---
  useEffect(() => {
    // 这是一个非常重要的模式:使用一个标志位来处理组件卸载的竞态条件。
    // 这可以防止在组件卸载后,异步请求完成时调用 setState 导致的内存泄漏警告。
    let isActive = true;

    const loadData = async () => {
      try {
        // 在 effect 内部,我们不直接修改外部的 state,而是先获取数据
        const data = await fetchData();

        // 当 Promise resolve 后,检查组件是否仍然挂载
        if (isActive) {
          setResult(data);
          setError(null);
        }
      } catch (err) {
        if (isActive) {
          setError(err.message);
        }
      } finally {
        if (isActive) {
          setLoading(false);
        }
      }
    };

    loadData();

    // 这是 useEffect 的 cleanup 函数。它在组件卸载时,或下一次 effect 执行前运行。
    // 在这里我们将标志位设为 false,中断任何可能的回调。
    return () => {
      isActive = false;
      console.log("Cleanup: Component unmounted or effect re-ran.");
    };
  }, []); // 空依赖数组,确保 effect 只在组件挂载时运行一次

  return (
    <div className="App">
      <h1>React File Explorer</h1>
      {loading && <p>Loading...</p>}
      {error && <p style={{ color: "red" }}>Error: {error}</p>}
      {!loading && !error && (
        <div>
          {result.map((item) => (
            // 使用稳定且唯一的 id 作为 key
            <Entry key={item.id} entry={item} depth={0} />
          ))}
        </div>
      )}
    </div>
  );
}