目录

  • 代码分割
  • React的懒加载
    • import() 原理
    • React.lazy 原理
    • Suspense 原理
  • 参考

1.代码分割

(1)为什么要进行代码分割?

现在前端项目基本都采用打包技术,比如 Webpack,JS逻辑代码打包后会产生一个 bundle.js 文件,而随着我们引用的第三方库越来越多或业务逻辑代码越来越复杂,相应打包好的 bundle.js 文件体积就会越来越大,因为需要先请求加载资源之后,才会渲染页面,这就会严重影响到页面的首屏加载。

而为了解决这样的问题,避免大体积的代码包,我们则可以通过技术手段对代码包进行分割,能够创建多个包并在运行时动态地加载。现在像 Webpack、 Browserify等打包器都支持代码分割技术。

(2)什么时候应该考虑进行代码分割?

这里举一个平时开发中可能会遇到的场景,比如某个体积相对比较大的第三方库或插件(比如JS版的PDF预览库)只在单页应用(SPA)的某一个不是首页的页面使用了,这种情况就可以考虑代码分割,增加首屏的加载速度。

2.React的懒加载

示例代码:

import React, { Suspense } from 'react';const OtherComponent = React.lazy(() => import('./OtherComponent'));function MyComponent() {return (<div><Suspense fallback={<div>Loading...</div>}><OtherComponent /></Suspense></div>);
}

如上代码中,通过 import() 、 React.lazy 和 Suspense 共同一起实现了 React 的懒加载,也就是我们常说了运行时动态加载,即 OtherComponent 组件文件被拆分打包为一个新的包(bundle)文件,并且只会在 OtherComponent 组件渲染时,才会被下载到本地。

那么上述中的代码拆分以及动态加载究竟是如何实现的呢?让我们来一起探究其原理是怎样的。

import() 原理

import() 函数是由TS39提出的一种动态加载模块的规范实现,其返回是一个 promise。在浏览器宿主环境中一个 import() 的参考实现如下:

function import(url) {return new Promise((resolve, reject) => {const script = document.createElement("script");const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);script.type = "module";script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;script.onload = () => {resolve(window[tempGlobal]);delete window[tempGlobal];script.remove();};script.onerror = () => {reject(new Error("Failed to load module script with URL " + url));delete window[tempGlobal];script.remove();};document.documentElement.appendChild(script);});
}

当 Webpack 解析到该 import() 语法时,会自动进行代码分割。

React.lazy 原理

以下 React 源码基于 16.8.0 版本

React.lazy 的源码实现如下:

export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {let lazyType = {$$typeof: REACT_LAZY_TYPE,_ctor: ctor,// React uses these fields to store the result._status: -1,_result: null,};return lazyType;
}

可以看到其返回了一个 LazyComponent 对象。

而对于 LazyComponent 对象的解析:

...
case LazyComponent: {const elementType = workInProgress.elementType;return mountLazyComponent(current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,);
}
...
function mountLazyComponent(_current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,
) { ...let Component = readLazyComponentType(elementType);...
}
// Pending = 0, Resolved = 1, Rejected = 2
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {const status = lazyComponent._status;const result = lazyComponent._result;switch (status) {case Resolved: {const Component: T = result;return Component;}case Rejected: {const error: mixed = result;throw error;}case Pending: {const thenable: Thenable<T, mixed> = result;throw thenable;}default: { // lazyComponent 首次被渲染lazyComponent._status = Pending;const ctor = lazyComponent._ctor;const thenable = ctor();thenable.then(moduleObject => {if (lazyComponent._status === Pending) {const defaultExport = moduleObject.default;lazyComponent._status = Resolved;lazyComponent._result = defaultExport;}},error => {if (lazyComponent._status === Pending) {lazyComponent._status = Rejected;lazyComponent._result = error;}},);// Handle synchronous thenables.switch (lazyComponent._status) {case Resolved:return lazyComponent._result;case Rejected:throw lazyComponent._result;}lazyComponent._result = thenable;throw thenable;}}
}

注:如果 readLazyComponentType 函数多次处理同一个 lazyComponent,则可能进入Pending、Rejected等 case 中。

从上述代码中可以看出,对于最初 React.lazy() 所返回的 LazyComponent 对象,其 _status 默认是 -1,所以 首次渲染 时,会进入 readLazyComponentType 函数中的 default 的逻辑,这里才会真正异步执行 import(url) 操作,由于并未等待,随后会检查模块是否 Resolved,如果已经Resolved了(已经加载完毕)则直接返回 moduleObject.default (动态加载的模块的默认导出),否则将通过 throw 将 thenable 抛出到上层。

为什么要 throw 它?这就要涉及到 Suspense 的工作原理,我们接着往下分析。

Suspense 原理

由于 React 捕获异常并处理的代码逻辑比较多,这里就不贴源码,感兴趣可以去看 throwException 中的逻辑,其中就包含了如何处理捕获的异常。简单描述一下处理过程,React 捕获到异常之后,会判断异常是不是一个 thenable,如果是则会找到 SuspenseComponent ,如果 thenable 处于 pending 状态,则会将其 children 都渲染成 fallback 的值,一旦 thenable 被 resolve 则 SuspenseComponent 的子组件会重新渲染一次。

为了便于理解,我们也可以用 componentDidCatch 实现一个自己的 Suspense 组件,如下:

class Suspense extends React.Component {state = {promise: null}componentDidCatch(err) {// 判断 err 是否是 thenableif (err !== null && typeof err === 'object' && typeof err.then === 'function') {this.setState({ promise: err }, () => {err.then(() => {this.setState({promise: null})})})}}render() {const { fallback, children } = this.propsconst { promise } = this.statereturn <>{ promise ? fallback : children }</>}
}

小结

至此,我们分析完了 React 的懒加载原理。简单来说,React利用 React.lazy 与 import() 实现了渲染时的动态加载 ,并利用 Suspense 来处理异步加载资源时页面应该如何显示的问题。

3.参考

代码分割– React

动态import - MDN - Mozilla

proposal-dynamic-import

React Lazy 的实现原理

深入理解React:懒加载(lazy)实现原理相关推荐

  1. swift_043(Swift 懒加载(lazy) )

    懒加载的优点 懒加载(lazy load),其实是延时加载,它的优点显而易见,首先,懒加载将对象的创建延迟到了需要对象的时候,这样减少了内存开销:其次,懒加载将创建对象.相关属性设置内聚在一个&quo ...

  2. react 16.6 懒加载 Lazy 尝鲜

    react 16.6 发布了新的功能 lazy ,和一个组件 Suspense 下面我们看一下他的用法 首先我们先创建两个组件 LazyTest.1 和 LazyTest.2,内容相同 import ...

  3. 深度剖析React懒加载原理

    目录 代码分割 React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 参考 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 ...

  4. SpringBoot实现懒加载@Lazy

    @Lazy使用说明 一般情况下,Spring容器在启动时会创建所有的Bean对象,使用@Lazy注解可以将Bean对象的创建延迟到第一次使用Bean的时候 使用方法 1.@Lazy(value = t ...

  5. 懒加载Lazy Loading

    "懒加载"也被叫作"延迟价值",它的核心思想是把对象的实例化延迟到真正调用该对象的时候,这样做的好处是可以减轻大量对象在实例化时对资源的消耗,而不是在程序初始化 ...

  6. 简单理解hibernate懒加载

    遇到问题代码 @GetMapping("/{id}")public Coffee getById(@PathVariable Long id) {Coffee coffee = c ...

  7. SAP Fiori里的List是如何做到懒加载Lazy load的

    今天一同事问我这个问题:S/4HANA Fiori应用里的列表,一旦Scroll到底部就会自动向后台发起新的请求把更多的数据读取到前台显示. 以Product Master这个应用为例,我点击搜索之后 ...

  8. swift -- 单例+ lazy懒加载 + 第三方库

    //工具类单例 static let goods : NHGoods = { let good = NHGoods() return good }() //懒加载 lazy var registerB ...

  9. [译]带你揭开Kotlin中属性代理和懒加载语法糖衣

    翻译说明: 原标题: How Kotlin's delegated properties and lazy-initialization work 原文地址: https://medium.com/t ...

  10. SpringBoot预加载与懒加载

    预加载 bean在springBoot启动过程中就完成创建加载 在AbstractApplicationContext的refresh方法中 // Instantiate all remaining ...

最新文章

  1. 测试发现equals和hashCode与书上描述的不一样
  2. AI一分钟 | 谷歌开发者大会,千人同玩AI小程序;阿里获杭州首张自动驾驶牌照...
  3. 苹果挖走Google人工智能一把手,他之前有多厉害?
  4. centos7安装ansible AWX17.1.0
  5. 再读《精通css》02:选择器
  6. P1429-平面最近点对(加强版)【分治】
  7. 创建新的option
  8. linux ll 按时间排序_Linux基本操作
  9. SQL计算宝宝吃奶的时间间隔(二)
  10. php取mysql某列的值,php – 获取MYSQL中某些列为null的表中的值
  11. centos7安装samba服务,以及设置权限分配
  12. Mongo散记--聚合(aggregation)amp; 查询(Query)
  13. 80X86寄存器详解
  14. HTML,CSS,font-family:中文字体的英文名称 (宋体 微软雅黑)
  15. 20个常用模拟电路(嵌入式硬件篇)
  16. 一文JDK动态代理的那点事儿
  17. EEPROM与FLASH闪存到底有什么区别?
  18. iOS:编译问题Presenting view controllers on detached view controllers is discouraged
  19. 利用百度身份证识别服务和python语言实现身份证信息的提取和保存
  20. 解压后在别的电脑能安装PS,在我电脑就提示安装文件损坏

热门文章

  1. java 拼图_Java 9:“拼图计划终于给了我们急需的Java安全带”
  2. Cisco2960交换机密码破解方法
  3. RTB广告展示分步说明
  4. 关于微信小程序自定义交易组件升级处理的相关问题,及解决思路
  5. 我的自定义知乎首页及问题页的样子
  6. Capstone CS5210规格书|低成本HDMI转VGA方案设计
  7. 计算机CPU像人的大脑,人脑与电脑的相似性与差异性及全球脑
  8. V4L2文档翻译(十一)
  9. 文献学习(part31)--Discovery of time-inconsecutive co-movement patterns of foreign currencies using ...
  10. 上海交通大学学生生存手册