React中如何让Suspense组件等待父组件useFetch请求完成?
问题:Suspense懒加载下,如何让依赖父组件请求结果的子组件延迟发起API调用
场景描述
在Country页面组件中包含CountryLanguages组件,该组件下又有LanguageList和LanguageTranslations两个子组件:
- 所有组件均通过
lazy结合Suspense实现懒加载,并配置了fallback占位组件 - CountryLanguages组件通过
useFetch钩子获取指定国家的语言列表 - LanguageList负责展示语言列表;LanguageTranslations通过另一个
useFetch钩子获取对应语言的翻译列表,其中languageIso参数由CountryLanguages传递,用于发起API请求
当前问题
首次加载时,LanguageTranslations组件初始化时languageIso为undefined,导致无法正常渲染。需要实现让被Suspense挂起的组件等待父组件的useFetch请求完成后,再发起自身的API调用。
相关代码
Country.jsx
// Country.jsx ... 省略导入代码 const CountryIntro = lazy(() => import('./countryintro/CountryIntro')); const CountryDetails = lazy(() => import('./countrydetails/CountryDetails')); const CountryLanguages = lazy(() => import('./countrylanguages/CountryLanguages') ); function Country() { return ( <> <Suspense fallback={<CountryIntroSkeleton />}> <CountryIntro /> </Suspense> <Suspense fallback={<CountryDetailsSkeleton />}> <CountryDetails /> </Suspense> <Suspense fallback={<CountryLanguagesSkeleton />}> <CountryLanguages /> </Suspense> </> ); } export default Country;
CountryLanguages.jsx
// CountryLanguages.jsx ... 省略导入代码 const LanguageList = lazy(() => import('./LanguageList')); const LanguageTranslations = lazy(() => import('./LanguageTranslations')); function CountryLanguages() { const countryIso = useCountryIso(); const { data } = useFetch(`/language/country/all?iso3=${countryIso}`); const [languageIso, setLanguageIso] = useState(data?.[0].iso); return ( <Section name="Languages"> <SectionHeader name="Languages" /> <Suspense fallback={<CountryLanguagesSkeleton />}> <LanguageList data={data} languageIso={languageIso} setLanguageIso={setLanguageIso} /> </Suspense> <Suspense fallback={<TranslationSkeleton />}> <LanguageTranslations languageIso={languageIso} /> </Suspense> </Section> ); } export default CountryLanguages;
LanguageList.jsx
// LanguageList.jsx function LanguageList({ data, languageIso, setLanguageIso }) { const languageList = data?.map((item) => { return ( <p key={item.id} onClick={() => setLanguageIso(item.iso)} className={item.iso === languageIso ? 'active' : ''}> {item.name} </p> ); }); return ( <> <div className="item-list"> <h3>Most Spoken</h3> <div>{languageList}</div> </div> </> ); } export default LanguageList;
LanguageTranslations.jsx
// LanguageTranslations.jsx ... 省略导入代码 function LanguageTranslations({ languageIso }) { const { data } = useFetch( `/language/translation/all?iso=${languageIso}`, {}, [languageIso] ); const phrases = data?.map((item) => { return ( <div key={item.id} className="country-language-phrase"> <p>{item.phrase}</p> <p className="red-text">{item.translation}</p> <p className="subtitle red-text"> {item.phonetic == null ? 'No phonetic spelling available' : item.phonetic} </p> <span className={`material-symbols-sharp ${item.link === null ? 'disabled' : ''}`} onClick={() => window.open(item.link, '_blank')} id="phrase-exit"> exit_to_app </span> </div> ); }); return ( <div className="country-language-translations"> <h3>Common Phrases</h3> <div>{phrases}</div> </div> ); } export default LanguageTranslations;
尝试过的方案及问题
曾尝试将两个调用API的组件包裹在同一个Suspense中,但问题依旧,且无法理解子组件顺序对请求等待顺序的影响。修改后的代码如下:
修改后的CountryLanguages.jsx
// CountryLanguages.jsx const LanguageList = lazy(() => import('./LanguageList')); const LanguageTranslations = lazy(() => import('./LanguageTranslations')); function CountryLanguages() { const [languageIso, setLanguageIso] = useState(null); return ( <Section name="Languages"> <SectionHeader name="Languages" /> <Suspense fallback={<CountryLanguagesSkeleton />}> <LanguageList languageIso={languageIso} setLanguageIso={setLanguageIso} /> <LanguageTranslations languageIso={languageIso} /> </Suspense> </Section> ); }
修改后的LanguageList.jsx
// LanguageList.jsx ... 省略导入代码 function LanguageList({ languageIso, setLanguageIso }) { const countryIso = useCountryIso(); const { data } = useFetch(`/language/country/all?iso3=${countryIso}`); useEffect(() => { setLanguageIso(data?.[0].iso); }, []); const languageList = data?.map((item) => { return ( <p key={item.id} onClick={() => setLanguageIso(item.iso)} className={item.iso === languageIso ? 'active' : ''}> {item.name} </p> ); }); return ( <> <div className="item-list"> <h3>Most Spoken</h3> <div>{languageList}</div> </div> </> ); }
此外,还遇到LanguageList组件中useEffect在渲染后未设置值的问题。
解决方案
核心思路
确保LanguageTranslations组件仅在languageIso有有效值时才发起API请求,同时修正异步请求与状态更新的时机逻辑,让Suspense与数据请求协同工作。
具体方案
1. 优化LanguageTranslations的请求触发条件
修改组件逻辑,仅当languageIso存在时才发起API请求,否则返回占位组件:
// LanguageTranslations.jsx ... 省略导入代码 function LanguageTranslations({ languageIso }) { // 仅当languageIso存在时才发起请求 const { data } = useFetch( languageIso ? `/language/translation/all?iso=${languageIso}` : null, {}, [languageIso] ); // 无有效值时返回占位 if (!languageIso) { return <TranslationSkeleton />; } const phrases = data?.map((item) => { return ( <div key={item.id} className="country-language-phrase"> <p>{item.phrase}</p> <p className="red-text">{item.translation}</p> <p className="subtitle red-text"> {item.phonetic == null ? 'No phonetic spelling available' : item.phonetic} </p> <span className={`material-symbols-sharp ${item.link === null ? 'disabled' : ''}`} onClick={() => window.open(item.link, '_blank')} id="phrase-exit"> exit_to_app </span> </div> ); }); return ( <div className="country-language-translations"> <h3>Common Phrases</h3> <div>{phrases}</div> </div> ); } export default LanguageTranslations;
2. 修正CountryLanguages的状态更新逻辑
原代码直接用未就绪的data初始化languageIso,改用useEffect监听data的变化,确保数据获取后再更新状态:
// CountryLanguages.jsx ... 省略导入代码 const LanguageList = lazy(() => import('./LanguageList')); const LanguageTranslations = lazy(() => import('./LanguageTranslations')); function CountryLanguages() { const countryIso = useCountryIso(); const { data } = useFetch(`/language/country/all?iso3=${countryIso}`); const [languageIso, setLanguageIso] = useState(null); // 监听data变化,获取到数据后设置默认languageIso useEffect(() => { if (data?.length > 0) { setLanguageIso(data[0].iso); } }, [data]); return ( <Section name="Languages"> <SectionHeader name="Languages" /> <Suspense fallback={<CountryLanguagesSkeleton />}> <LanguageList data={data} languageIso={languageIso} setLanguageIso={setLanguageIso} /> </Suspense> {/* 仅当languageIso存在时才渲染组件 */} {languageIso && ( <Suspense fallback={<TranslationSkeleton />}> <LanguageTranslations languageIso={languageIso} /> </Suspense> )} </Section> ); } export default CountryLanguages;
3. 修正LanguageList的useEffect依赖
原代码中useEffect依赖数组为空,导致data更新后无法触发状态更新,需要添加data和setLanguageIso到依赖数组:
// LanguageList.jsx ... 省略导入代码 function LanguageList({ languageIso, setLanguageIso }) { const countryIso = useCountryIso(); const { data } = useFetch(`/language/country/all?iso3=${countryIso}`); useEffect(() => { if (data?.length > 0) { setLanguageIso(data[0].iso); } }, [data, setLanguageIso]); const languageList = data?.map((item) => { return ( <p key={item.id} onClick={() => setLanguageIso(item.iso)} className={item.iso === languageIso ? 'active' : ''}> {item.name} </p> ); }); return ( <> <div className="item-list"> <h3>Most Spoken</h3> <div>{languageList}</div> </div> </> ); }
方案说明
- 通过条件渲染控制LanguageTranslations的加载时机,避免无效API请求
- 用
useEffect监听异步数据的变化,确保状态更新时机准确 - 修正
useEffect依赖数组,保证状态能随数据变化同步更新
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

