suspense 和 lazy 配合实现懒加载的原理浅析

先看下面这段代码

javascript 复制代码
// lazy.js
function Fn() {
  return <div className="App">
    函数组件
  </div>
}

// app.js
// 添加一个固定的延迟时间,以便你可以看到加载状态
function delayForDemo(promise) {
  return new Promise(resolve => {
    setTimeout(resolve, 5000);
  }).then(() => promise);
}

const LazyFn = lazy(() => delayForDemo(import('./lazy.js')));
function SuspendedApp() {
  return (
    <Suspense fallback={<div>加载中。。。</div>}>
      <LazyFn />
    </Suspense>
  )
}


const root = ReactDOM.createRoot(document.getElementById('app'));

root.render(
  <SuspendedApp />
);

这段代码模拟了懒加载的过程,运行之后,屏幕上会先展示 Suspense 的 fallback ------ 加载中。。。 过 5 秒钟加载到组件后会自动重新渲染屏幕上出现 函数组件。。。。那这个 React 是怎么实现的呢,下面我们来看看实现的流程。

实现流程

这个流程发生在 Render 阶段。

  1. 从根节点开始挨个执行 beginWork 方法(不太清楚的话可以看看Render流程
  2. 执行到 Suspense 的 beginWork 时,会通过 Didcaptrue 来判断是否展示 fallback 内容,第一次执行默认不展示 fallback,走正常渲染子节点
  3. 正常渲染 lazy 节点,结果返回一个 Promise,Promise 肯定没办法渲染啊,所以把这个Promise作为异常抛出
  4. 异常被捕获,发现异常是一个 Promise,为这个 Promise.then 传入了一个触发更新的方法,并找到距离这个 lazy 组件最近的 Suspense 组件并标记 shouldCapture 为 true,后续处理后标记 DidCaptrue 为 true
  5. 重新执行 Suspense 的 beginWork 方法,此时第二次执行,Didcaptrue 为 true ,展示 fallback 内容,同时把 Didcaptrue 置为 false。
  6. 懒加载的组件加载完成,触发更新,和步骤 1 一样
  7. 执行 Suspense 的 beginWork 方法,DidCaptrue 为 false,正常展示子节点
  8. 渲染 lazy 节点,此时拿到了组件,正常渲染组件

以上就是主要的实现流程(当然实际会比上面介绍的复杂一点)。

源码环节

先是 Suspense 执行 beginWork

这里就是通过 Didcaptrue 来判断 showFallback 的值的。 第一次执行 showFallback 为 false ,因此先正常展示子节点

之后会处理 OffscreenComponent, 完了才会处理 lazy 组件,这个方法里最关键的就是 init 方法 也是在 init 方法里面抛出了异常

接下来是捕获异常,先是被beginWork 捕获,再抛出,最终被 renderRootSync 函数的错误处理捕获处理

接下来重新处理 Suspense,此时showFallback 为 true,同时处理了子节点和 Fallback 节点,最终返回Fallback节点,并渲染

接下来Promise被解决

再次处理 Suspense,更新子节点

然后处理 OffscreenComponent,再处理 lazy 节点

然后就是正常的渲染流程了,流程结束 懒加载的组件 替换 fallback 展示到页面上

最后

以上就是我梳理的 suspense 和 lazy 组合的工作流程,感谢大家的阅读,有不对的地方也欢迎大家指出来。

参考

  • React 18.2.0 源码
相关推荐
海晨忆6 分钟前
【Vue】v-if和v-show的区别
前端·javascript·vue.js·v-show·v-if
JiangJiang30 分钟前
🚀 Vue人看React useRef:它不只是替代 ref
javascript·react.js·面试
1024小神35 分钟前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
龙骑utr39 分钟前
qiankun微应用动态设置静态资源访问路径
javascript
Jasmin Tin Wei40 分钟前
css易混淆的知识点
开发语言·javascript·ecmascript
齐尹秦43 分钟前
CSS 列表样式学习笔记
前端
wsz77771 小时前
js封装系列(一)
javascript
Mnxj1 小时前
渐变边框设计
前端
用户7678797737321 小时前
由Umi升级到Next方案
前端·next.js
快乐的小前端1 小时前
TypeScript基础一
前端