如何在React中结合Suspense与JSON动态导入?解决无限循环问题
问题原因分析
你的代码陷入无限循环的核心原因有两个:
- 每次组件渲染时,
getData(year)都会创建全新的wrapPromise实例,状态始终初始化为pending,调用read()就会抛出suspender,React捕获后会重新渲染组件,形成循环。 - 动态导入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
相关产品推荐
相关产品推荐

