闽公网安备 35020302035485号
// App.tsx
// 堆代码 duidaima.com
import { Suspense, lazy } from "react";
const LazyCpn = lazy(() => import("./Lazy"));
function App() {
return (
<Suspense fallback={<div>外层加载...</div>}>
<LazyCpn />
</Suspense>
);
}
export default App;
Lazy.tsx导出的LazyComponent大体代码如下:// Lazy.tsx
function LazyComponent() {
const ChildComponent = useMemo(() => {
// ...省略逻辑
}, []);
return ChildComponent;
}
export default LazyComponent;
可以发现,LazyComponent组件的子组件是useMemo的返回值,而这个useMemo的依赖项是[](没有依赖项),理论上来说useMemo的回调只会执行一次。const ChildComponent = useMemo(() => {
const LazyCpn = lazy(
() => Promise.resolve({ default: () => <div>子组件</div>})
)
return (
<Suspense fallback={<div>内层加载...</div>}>
<LazyCpn />
</Suspense>
);
}, []);
简单来说,useMemo会返回一个「被Suspense包裹的懒加载组件」。
是不是看起来比较绕,没关系,我们看看整个Demo的结构图:
.整个应用有两层Suspense,两层React.lazy

const ChildComponent = useMemo(() => {
console.log("useMemo回调执行啦")
// ...省略代码
}, []);
再次重申,这个useMemo的依赖项是不会变的。
function LazyComponent() {
console.log("LazyComponent render")
useEffect(() => {
console.log("LazyComponent mount");
}, []);
const ChildComponent = useMemo(() => {
// ...省略逻辑
}, []);
return ChildComponent;
}
会发现:// 堆代码 duidaima.com
<ErrorBoundary>
<A>
<B/>
</A>
</ErrorBoundary>
更新进行到ErrorBoundary时,是不知道是否应该渲染「报错对应的UI」,只有继续遍历A、B,报错以后,才知道ErrorBoundary需要渲染成「报错对应的UI」。<Suspense fallback={<div>加载...</div>}>
<A>
<B/>
</A>
</Suspense>
更新进行到Suspense时,是不知道是否应该渲染「fallback对应的UI」,只有继续遍历A、B,发生挂起后,才知道Suspense需要渲染成「fallback对应的UI」。对于上述两种情况,React中存在一种「在同一个更新中的回溯,重试机制」,被称为unwind流程。在Demo中,就是遭遇了上千次的unwind。<ErrorBoundary>
<A>
<B/>
</A>
</ErrorBoundary>
正常更新流程是:






// App.tsx
const LazyCpn = lazy(() => import("./Lazy"));
内层的React.lazy是在useMemo回调中定义的:
const ChildComponent = useMemo(() => {
const LazyCpn = lazy(
() => Promise.resolve({ default: () => <div>子组件</div>})
)
return (
<Suspense fallback={<div>内层加载...</div>}>
<LazyCpn />
</Suspense>
);
}, []);
前者的引用是稳定的,而后者每次执行useMemo回调都会生成新的引用。这意味着当unwind进入Suspense,重新往下更新,更新进入到LazyComponent后,useMemo回调执行,创建新的React.lazy,又会进入unwind流程: