ES12 中的 Dynamic Import:如何实现按需加载

随着 Web 应用的复杂度越来越高,前端应用的加载速度已经成为了用户体验的重要指标。为了提升页面的加载速度,很多开发者会采用尽可能减小资源文件体积和按需加载的策略。ES2015 引入了模块化,对于前端应用的组织和管理带来了极大的便利。而 ES12 中新增的动态导入功能,则更好的满足了按需加载的需求。

Dynamic Import 的概念及特性

ES12 中的动态导入是指在运行时根据某些条件动态加载模块,而不是在编译时就把所有依赖的模块都加载进来。通过动态导入,我们可以更加灵活地控制前端应用的加载,从而提升用户访问页面的体验。

Dynamic Import 的基本语法如下:

import(`模块路径`).then(module => {
  // 模块加载成功
}).catch(error => {
  // 模块加载失败
})

通过 import() 函数可以动态加载模块,并返回一个 Promise。当模块加载成功后,module 参数就是加载的模块对象,我们可以像普通的模块一样使用其中的函数和变量。如果加载失败,则会进入 catch 语句块。

动态导入有如下几个特性:

  1. 可以动态加载某个模块,避免把所有依赖都一开始加载进来。
  2. Promise 风格的 API,更符合异步编程的规范。
  3. 加载后的模块不会提前执行,只有在代码中真正需要使用时才会执行,从而减小了页面的加载时间和内存占用。

Dynamic Import 的应用场景

在实际的开发中,动态导入有很多用处。下面介绍几种常见的场景。

按需加载

按需加载是 dynamic import 的主要使用场景。在单页面应用(SPA)中,页面很多时,一开始就把所有的脚本都加载进内存是不现实的,特别是在移动端设备上,资源和内存的消耗更为明显。这个时候,我们可以使用动态导入模块的方式,等到当前页面需要用到某些模块时再去加载,避免了不必要的网络和计算资源消耗。

例如,假设我们有两个组件,在页面一开始时不需要全部加载,而是等到需要使用时才加载:

import('./module1.js')
  .then(module1 => {
    module1.foo()
  })
  .catch(err => {
    console.error("模块加载失败 ", err)
  })

// 点击按钮时再动态加载模块2
document.getElementById('btn').addEventListener('click', async function () {
  const module2 = await import('./module2.js');
  module2.bar();
});

优化 App 首屏加载速度

在提高用户体验的同时,降低应用的首屏加载速度也是一个非常重要的目标。因此,在整个应用中,我们需要根据不同的场景和用户需求来决定哪些模块可以先加载,哪些模块可以等到需要时再去加载。为了实现这个目标,我们可以根据路由信息、浏览器判断等动态选择需要加载哪些模块。

// 加载控制器
app.use((req, res, next) => {
  if (req.path === "/home") {
    import('./homeController.js')
      .then(controller => {
        controller.renderView(req, res)
      })
      .catch(err => {
        console.error('模块加载失败', err);
      });
  } else {
    next();
  }
})

根据环境判断加载不同的模块

在实际开发中,我们经常需要为不同的环境准备不同的代码。比如在开发环境时,我们可能需要使用 webpack-dev-server,而在生产环境下则需要使用真正的服务器。这个时候,我们可以使用动态导入的方式根据当前环境的不同来加载不同的模块。

async function initApp() {
  let codeSplitting = false;
  if (process.env.NODE_ENV !== "production") {
    const { default: setupDevServer } = await import(
      "path-to-dev-server/setupDevServer.js"
    );
    setupDevServer(app);
    console.log("开发模式 - 开启热更新...");
    codeSplitting = true;
  } else {
    console.log("生产模式...");
  }

  const { default: setupRoutes } = await import(
    codeSplitting ?
    "path-to-routes/index.js" :
    "path-to-routes/server/index.js"
  );

  setupRoutes(app);
}

initApp();

如何使用 Dynamic Import

在实际的代码中,可以把需要动态加载的代码放到一个单独的文件里,然后使用动态导入的方式去执行。

以下是一个具体的使用示例。

创建一个 demo.js 应用:

其中 demo.js 代码:

async function initApp() {
  let codeSplitting = false;
  if (process.env.NODE_ENV !== "production") {
    const { default: setupDevServer } = await import(
      "path-to-dev-server/setupDevServer.js"
    );
    setupDevServer(app);
    console.log("开发模式 - 开启热更新...");
    codeSplitting = true;
  } else {
    console.log("生产模式...");
  }

  const { default: setupRoutes } = await import(
    codeSplitting ?
    "path-to-routes/index.js" :
    "path-to-routes/server/index.js"
  );

  setupRoutes(app);
}

initApp();

启动 demo.js 应用:

总结

Dynamic Import 是 ES12 的一个功能,解决了在前端应用中按需加载模块的问题,帮助开发者更好地优化前端应用的加载性能。通过本文的详细讲解,我们已经了解了 Dynamic Import 的概念及特性、使用场景,以及如何实现按需加载的功能。希望本文能够帮助你更好地使用 Dynamic Import 进行前端开发。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65921576eb4cecbf2d6fd046


纠错反馈