父节点挂载后条件渲染懒加载组件引发无限循环问题
React 18.3.1 懒加载组件引发无限循环问题
复现步骤
- 创建组件,仅在获取到div节点引用后,才在该div内渲染子组件(原代码将div节点存入state)
- 传入懒加载组件作为子元素
示例代码
import { Suspense, lazy, useState } from 'react'; const LazyLoadedComponent = lazy( () => new Promise((resolve) => { setTimeout( () => resolve({ default: () => <div>Lazy loaded component</div>, }), 500, ); }), ); const RenderAfterMount = (props) => { const [node, setNode] = useState(null); return <div ref={setNode}>{node && props.children}</div>; }; export const App = () => ( <Suspense> <RenderAfterMount> <LazyLoadedComponent /> </RenderAfterMount> </Suspense> );
当前行为
运行时出现无限循环错误
预期行为
成功渲染懒加载组件
问题分析与解决方案
问题根源在于将DOM节点存入state:当node从null变为DOM节点时,会触发组件重渲染并开始渲染懒加载组件。此时Suspense会捕获组件的加载状态,触发React暂停渲染并重新尝试,这个过程中div的ref回调会再次执行,调用setNode更新state,进而再次触发重渲染,形成无限循环。
解决方法是改用useRef存储DOM节点,因为ref的更新不会触发组件重渲染:
import { Suspense, lazy, useRef, useEffect, useState } from 'react'; const LazyLoadedComponent = lazy( () => new Promise((resolve) => { setTimeout( () => resolve({ default: () => <div>Lazy loaded component</div>, }), 500, ); }), ); const RenderAfterMount = (props) => { const nodeRef = useRef(null); const [isMounted, setIsMounted] = useState(false); useEffect(() => { if (nodeRef.current) { setIsMounted(true); } }, []); return <div ref={nodeRef}>{isMounted && props.children}</div>; }; export const App = () => ( <Suspense fallback={<div>Loading...</div>}> <RenderAfterMount> <LazyLoadedComponent /> </RenderAfterMount> </Suspense> );
通过useRef存储DOM节点,配合useEffect在组件挂载后仅触发一次状态更新,就能避免state反复更新导致的无限循环,同时正常渲染懒加载组件。
内容的提问来源于stack exchange,提问作者Suren Poghosyan
相关产品推荐
相关产品推荐

