Skip to content

react中懒加载的实现原理是什么?

参考答案:

随着前端应用体积的扩大,资源加载的优化是我们必须要面对的问题,动态代码加载就是其中的一个方案,webpack 提供了符合 ECMAScript 提案 的 import() 语法 ,让我们来实现动态地加载模块(注:require.ensure 与 import() 均为 webpack 提供的代码动态加载方案,在 webpack 2.x 中,require.ensure 已被 import 取代)。

在 React 16.6 版本中,新增了 React.lazy 函数,它能让你像渲染常规组件一样处理动态引入的组件,配合 webpack 的 Code Splitting ,只有当组件被加载,对应的资源才会导入 ,从而达到懒加载的效果。

使用 React.lazy

在实际的使用中,首先是引入组件方式的变化:

javascript
// 不使用 React.lazy
import OtherComponent from './OtherComponent';
// 使用 React.lazy
const OtherComponent = React.lazy(() => import('./OtherComponent'))

React.lazy 接受一个函数作为参数,这个函数需要调用 import() 。它需要返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

react.js
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 组件渲染时,才会被下载到本地。

需要注意的一点是,React.lazy 需要配合 Suspense 组件一起使用,在 Suspense 组件中渲染 React.lazy 异步加载的组件。如果单独使用 React.lazy,React 会给出错误提示。

Suspense 可以包裹多个动态加载的组件,这也意味着在加载这两个组件的时候只会有一个 loading 层,因为 loading 的实现实际是 Suspense 这个父组件去完成的,当所有的子组件对象都 resolve 后,再去替换所有子组件。这样也就避免了出现多个 loading 的体验问题。所以 loading 一般不会针对某个子组件,而是针对整体的父组件做 loading 处理。

Webpack 动态加载

上面使用了 import() 语法,webpack 检测到这种语法会自动代码分割。使用这种动态导入语法代替以前的静态引入,可以让组件在渲染的时候,再去加载组件对应的资源,这个异步加载流程的实现机制是怎么样呢?

import() 原理

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

javascript
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 通过创建 script 标签来实现动态加载的,找出依赖对应的 chunk 信息,然后生成 script 标签来动态加载 chunk,每个 chunk 都有对应的状态:未加载 、 加载中、已加载 。

我们可以运行 React.lazy 代码来具体看看 network 的变化,为了方便辨认 chunk。我们可以在 import 里面加入 webpackChunckName 的注释,来指定包文件名称。

Suspense 组件

Suspense 内部主要通过捕获组件的状态去判断如何加载, React.lazy 创建的动态加载组件具有 Pending、Resolved、Rejected 三种状态,当这个组件的状态为 Pending 时显示的是 Suspense 中 fallback 的内容,只有状态变为 resolve 后才显示组件。

Error Boundaries 处理资源加载失败场景

如果遇到网络问题或是组件内部错误,页面的动态资源可能会加载失败,为了优雅降级,可以使用 Error Boundaries 来解决这个问题。

Error Boundaries 是一种组件,如果你在组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 生命周期函数,它就会成为一个 Error Boundaries 的组件。

总结

React.lazy() 和 React.Suspense 的提出为现代 React 应用的性能优化和工程化提供了便捷之路。

React.lazy 可以让我们像渲染常规组件一样处理动态引入的组件,结合 Suspense 可以更优雅地展现组件懒加载的过渡动画以及处理加载异常的场景。

题目要点:

在 React 中,懒加载(Lazy Loading)是一种按需加载组件的技术,可以优化应用的性能,减少初次加载时间。

React 提供了内置的功能来实现懒加载,主要通过 React.lazySuspense 组件来完成。

1. 懒加载的基本原理

1.1 懒加载定义

  • 定义:懒加载是一种在需要时才加载资源(如组件)的策略,而不是在应用初始加载时一次性加载所有资源。这样可以减少初始加载时间和网络带宽的消耗,提高应用的性能。

1.2 懒加载的工作原理

  • 动态导入:使用 import() 函数来动态加载模块(组件)。import() 返回一个 Promise,组件会在 Promise resolve 后才被加载。
  • 组件拆分:将大组件或不常用的组件拆分成更小的部分,这样只有在需要时才加载它们。
  • 代码拆分:与懒加载相关的一个概念,通过打包工具(如 Webpack)将代码分割成多个文件,按需加载。

2. React 中懒加载的实现

2.1 React.lazy

  • 功能React.lazy 用于定义一个懒加载的组件。它接收一个函数,该函数使用 import() 动态导入组件模块。

  • 示例

    javascript
    import React, { Suspense } from 'react';
    
    // 使用 React.lazy 定义懒加载组件
    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    
    function App() {
      return (
        <div>
          <h1>My App</h1>
          <Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
          </Suspense>
        </div>
      );
    }
    
    export default App;

2.2 Suspense

  • 功能Suspense 组件用于包裹懒加载组件,并定义一个 fallback UI,当懒加载组件还未加载完成时,显示该 UI。

  • 示例(在上面的例子中已经包含):

    javascript
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>

3. 懒加载的实现原理

3.1 动态导入

  • 动态导入import() 函数用于动态加载模块。它返回一个 Promise,该 Promise 在模块加载完成后解析。React 使用这个特性来实现懒加载。
  • 模块分割:现代 JavaScript 打包工具(如 Webpack)可以将应用拆分成多个代码块。import() 用于按需加载这些代码块。

3.2 代码分割

  • 定义:代码分割是将应用代码分成多个文件的过程,这些文件可以按需加载,从而减少初始加载时间。
  • 实现:使用打包工具(如 Webpack)的代码分割功能,结合 React 的懒加载来实现。

4. 注意事项

4.1 路由组件懒加载

  • 实现:在 React Router 中,可以结合 React.lazySuspense 来实现路由组件的懒加载。

  • 示例

    javascript
    import React, { Suspense } from 'react';
    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
    
    const Home = React.lazy(() => import('./Home'));
    const About = React.lazy(() => import('./About'));
    
    function App() {
      return (
        <Router>
          <Suspense fallback={<div>Loading...</div>}>
            <Switch>
              <Route path="/about" component={About} />
              <Route path="/" component={Home} />
            </Switch>
          </Suspense>
        </Router>
      );
    }
    
    export default App;

4.2 服务器端渲染(SSR)

  • 注意React.lazySuspense 主要用于客户端渲染。在服务器端渲染中,需要使用不同的技术来处理懒加载组件的渲染,如使用 loadable-components 库。

5. 总结

  • 懒加载:按需加载组件,优化应用性能和用户体验。
  • 实现:通过 React.lazySuspense 实现懒加载,使用动态导入和代码分割。
  • 注意事项:懒加载在客户端渲染中非常有效,但在服务器端渲染中需要额外处理。