React懒加载实现原理深入分析

目录
  • 1.代码分割
  • 2.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.lazySuspense 共同一起实现了 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,
  );
}
...
...
case LazyComponent: {
  const elementType = workInProgress.elementType;
  return mountLazyComponent(
    current,
    workInProgress,
    elementType,
    updateExpirationTime,
    renderExpirationTime,
  );
}
...
// 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;
    }
  }
}

参考React实战视频讲解:进入学习

注:如果 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 是否是 thenable
    if (err !== null && typeof err === 'object' && typeof err.then === 'function') {
      this.setState({ promise: err }, () => {
        err.then(() => {
          this.setState({
            promise: null
          })
        })
      })
    }
  }
  render() {
    const { fallback, children } = this.props
    const { promise } = this.state
    return <>{ promise ? fallback : children }</>
  }
}

小结

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

到此这篇关于React懒加载实现原理深入分析的文章就介绍到这了,更多相关React懒加载内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React 路由懒加载的几种实现方案

    这篇文字简单的介绍了React在路由懒加载方面的几种实现方案. 传统的两种方式 import() 符合ECMAScript提议的import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象.这意味着模块时异步加载的 webpack v2+ 使用 使用方式 function component() { return import( /* webpackChunkName: "lodash" */ 'lodash').then(_

  • React 悬浮框内容懒加载实例详解

    目录 界面隐藏 懒加载 React实现 原始代码 放入新的DIV 状态设置 样式设置 事件设置 事件优化 延迟显示悬浮框 悬浮框内容懒加载 完整代码 界面隐藏 一个容器放置视频,默认情况下 display: none; z-index: 0; transform: transform3d(10000px, true_y, true_z); y轴和z轴左边都是真实的(腾讯视频使用绝对定位,因此是计算得到的),只是将其移到右边很远的距离. 懒加载 React监听鼠标移入(获取坐标) 添加事件监听 o

  • react如何用懒加载减少首屏加载时间

    最近在写一个react-ant-admin的集成框架用于快速搭载中后台项目.其中遇到很多问题,最重要的应该是访问速度了.我就想 react 可不可以和 vue 一样用路由懒加载来减少首页渲染所花费的时间. 于是找到了一个很好用的轮子:@loadable/component 使用 安装 npm install @loadable/component -D # or use yarn yarn add @loadable/component -D 如何在路由中使用? 在src/router/inde

  • React中Suspense及lazy()懒加载及代码分割原理和使用方式

    目录 React.lazy() 概括 为什么需要懒加载 如何进行代码分割 Suspense Suspense应用场景 Suspense实现原理 总结 Suspense和lazy()都是react中比较新的特性,在项目中使用还比较少,但是学习一下有助于在后面的项目中使用,同样可以一窥React未来的发展方向 React.lazy() 概括 顾名思义lazy()方法是用来对项目代码进行分割,懒加载用的.只有当组件被加载,内部的资源才会导入 为什么需要懒加载 在React的项目中import导入其他组

  • React懒加载实现原理深入分析

    目录 1.代码分割 2.React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 小结 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 Webpack,JS逻辑代码打包后会产生一个 bundle.js 文件,而随着我们引用的第三方库越来越多或业务逻辑代码越来越复杂,相应打包好的 bundle.js 文件体积就会越来越大,因为需要先请求加载资源之后,才会渲染页面,这就会严重影响到页面的首屏加载. 而为了解决这样的问题,避免大体

  • Vue中使用import进行路由懒加载的原理分析

    目录 使用import进行路由懒加载的原理 (1)遵循规范 (2)调用时间 (3)本质 vue路由懒加载,使用import无法处理 解决 使用import进行路由懒加载的原理 首先我们来说说,import 和 require 的区别 node 编程中最重要的思想就是模块化,import 和 require 都是被模块化所使用. (1)遵循规范 require是 AMD规范引入方式 import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法 (2)调用时间 require是运行

  • 前端JS图片懒加载原理方案详解

    目录 背景 原理 方案 方案一:img的loading属性设为“lazy” 使用方法 优点 兼容性 缺点 方案二:通过offsetTop来计算是否在可视区域内 优化 优点 缺点 方案三:通过getBoundingClientRect来计算是否在可视区域内 方案四:使用IntersectionObserver来判断是否在可视区域内 兼容性 优点 缺点 问题 布局抖动 响应式图片 SEO不友好 插件 背景 懒加载经常出现在前端面试中,是前端性能优化的常用技巧.懒加载也叫延迟加载,把非关键资源先不加载

  • JavaScript懒加载与预加载原理与实现详解

    目录 1.懒加载 1.1.什么是懒加载 1.2.为什么要使用懒加载 1.3.懒加载的优点 1.4.懒加载的原理 1.5.懒加载的实现步骤 1.6.懒加载的实现方式 2.预加载 2.1.什么是预加载 2.2.为什么要使用预加载 2.3.实现预加载的方法 1.懒加载 1.1.什么是懒加载 懒加载也可以叫做延迟加载,当访问一个页面的时候,先把img元素伙食其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样只需要请求一次,俗称占位图): 只有当图片出现在浏览器的可视区域内时,才这只图片真正

  • JavaScript如何实现图片懒加载(lazyload) 提高用户体验(增强版)

    目录: 懒加载的意义(为什么要使用懒加载) 原理 代码 在上篇文章给大家介绍了JavaScript实现图片懒加载(Lazyload),大家可以参考下. 懒加载的意义(为什么要使用懒加载) 对页面加载速度影响最大的就是图片,一张普通的图片可以达到几M的大小,而代码也许就只有几十KB.当页面图片很多时,页面的加载速度缓慢,几S钟内页面没有加载完成,也许会失去很多的用户. 所以,对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区

  • JS图片懒加载库VueLazyLoad详解

    目录 背景 说明 实现原理 1. placeholder 的实现很细致和灵活 2. 添加图片缓存 3. 事件监听使用节流 4. 监听事件不止滚动事件 5. 事件列队的方式来处理懒加载 6. 支持 data-srcset 7. 自定义控制可视区的判定范围 待完善 1. 没有解决布局抖动 2. 跳过已经加载图片的判断方式 3. 局部懒加载 4. 性能不是很好 5. observer 模式配置简单 6. SEO 不友好 总结 背景 上篇<图片懒加载原理方案详解>中详细解析了图片懒加载的原理和方案.主

  • vue前端性能优化之预加载和懒加载示例详解

    目录 预加载 图片预加载 JS预加载 js的加载方式 preload prefetch Preload & Prefetch 的区别 不同资源加载的优先级规则 懒加载 图片懒加载 路由懒加载 组件懒加载 最后 预加载 预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源:我们使用该技术预先告知浏览器,等下某些资源可能要被使用,先把资源下载下来,不要等使用的时候再下载,可以看出这样的加载技术会增加服务器的压力,但是用户的体验会比较好,因为可以较快的看到后面的

随机推荐