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

如何在React中结合Suspense与JSON动态导入?解决无限循环问题

问题原因分析

你的代码陷入无限循环的核心原因有两个:

  1. 每次组件渲染时,getData(year)都会创建全新的wrapPromise实例,状态始终初始化为pending,调用read()就会抛出suspender,React捕获后会重新渲染组件,形成循环。
  2. 动态导入JSON文件时,实际的JSON内容在模块的default属性下,你直接访问data.daily会导致后续渲染报错,进一步加剧渲染循环。
解决方案

以下是修正后的代码,核心思路是缓存已加载的结果,并调整组件结构让Suspense正确工作:

import { Suspense, useMemo } from 'react';

interface DailyTableProps {
  year?: number;
  month?: number;
}

// 全局缓存:存储不同年份对应的加载结果,避免重复发起请求
const dataCache: Record<number, ReturnType<typeof wrapPromise>> = {};

function wrapPromise(promise: Promise<any>) {
  let status = "pending", res: any;
  const suspender = promise.then(
    (resolve) => {
      status = "resolved";
      res = resolve.default; // 动态导入的JSON内容在default属性中
    },
    (err) => {
      status = "error";
      res = err;
      console.error(err);
    }
  );
  return {
    read() {
      switch (status) {
        case "pending":
          throw suspender;
        case "error":
          throw res;
        case "resolved":
          return res;
        default:
          throw new Error("未知状态");
      }
    },
  };
}

function getData(year: number) {
  // 缓存命中则复用,否则创建新的加载器
  if (!dataCache[year]) {
    dataCache[year] = wrapPromise(import(`./assets/${year}.json`));
  }
  return dataCache[year];
}

// 拆分出子组件:负责读取数据和渲染,让Suspense正确捕获加载状态
function DailyContent({ year }: { year: number }) {
  const data = getData(year).read();
  return (
    <>
      {data.daily?.length > 0 && (
        <ul>
          {data.daily.month[0].map((v: any, k: number) => (
            <li key={k}>{v}</li>
          ))}
        </ul>
      )}
    </>
  );
}

export default function DailyTable(props: DailyTableProps) {
  const { year = 2023, month = 0 } = props;

  return (
    <Suspense fallback={<p>loading</p>}>
      <DailyContent year={year} />
    </Suspense>
  );
}
关键修改点说明
  • 全局缓存dataCache:同一个年份只会发起一次JSON导入请求,避免重复创建promise和加载器,从根源上解决循环问题。
  • 拆分子组件DailyContent:将读取数据的逻辑独立到子组件中,确保Suspense捕获到suspender时,只重新渲染子组件而非整个父组件。
  • 修正JSON访问路径:动态导入的JSON模块,实际内容存储在default属性下,必须通过resolve.default获取。
  • 可选替代方案:如果不想用全局缓存,也可以在父组件中用useMemo缓存加载器:
    const dataLoader = useMemo(() => getData(year), [year]);
    
    再将dataLoader传入子组件调用read(),效果一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 02:37:43