跳至主要內容

React 实现按需加载

YihuiReactReact Hooks大约 3 分钟

React 实现按需加载

如何控制好整个应用的大小,以及如何提升加载性能,才能确保应用的打开速度够快。

照此来看,为了提高首屏加载速度,我们就需要对应用进行分包。首先,在打开某个页面时,只加载这个页面相关的内容,也就是按需加载。同时,为了提升后续应用的打开速度,就需要采用高效的缓存策略,避免前端资源的重复下载。

如何实现按需加载?

使用 import 语句,定义按需加载的起始模块

所谓按需加载,就是指在某个组件需要被渲染到页面时,才会去实际地下载这个页面,以及这个页面依赖的所有代码。比如当用户打开 /users/profile 这个页面时,才会真正加载对应组件的代码。如下图所示,就展示了这样一个结构,其中 Profile Page 就是需要动态加载的组件。

img

要实现这个功能,本质上在于,我们需要有动态加载模块的能力。也就是在运行时去加载模块,而不是静态地去 import 一个模块。对于这个需求,ECMA Script 标准有一个提案,专门用于动态加载模块,语法是 import(someModule)。

注意,这里的 import 和我们一般用于引入模块的静态声明方式不同,比如 import something from 'somemodule' 。但这里的 import 是作为一个函数动态运行的,这个 import() 函数会返回一个 Promise。这样,在模块加载成功后,我们就可以在 Promise 的 then 回调函数中去使用这个模块了。

虽然这只是一个提案,并没有成为标准,但是 Webpack 等打包工具利用了这样的语法去定义代码的分包。也就是说,Webpack 实现了这样的语法。

function ProfilePage() {
  // 定义一个 state 用于存放需要加载的组件
  const [RealPage, setRealPage] = useState(null);
  
  // 根据路径动态加载真正的组件实现
  import('./RealProfilePage').then((comp) => {
    setRealPage(Comp);
  });
  // 如果组件未加载则显示 Loading 状态
  if (!RealPage) return 'Loading....';
  
  // 组件加载成功后则将其渲染到界面
  return <RealPage />
}

在这段代码中,我们定义了 ProfilePage 这样一个实现按需加载功能的组件。这个组件只有被执行时,也就是渲染到界面上时,才会真正加载具体的实现代码。

这里要理解的关键点就在于,**import() 这个语句完全是由 Webpack 进行处理的。**Webpack 会将以“./RealProfilePage”模块为起点的所有依赖模块,单独打成一个包。并且,Webpack 还会生成代码,用于按需加载这个模块。

理解了这一点,我们也就知道了按需加载的实现原理:Webpack 利用了动态 import 语句,自动实现了整个应用的拆包。而我们在实际开发中,其实并不需要关心 Webpack 是如何做到的,而只需要考虑:该在哪个位置使用 import 语句去定义动态加载的拆分点。

总体需要采用的策略是:按业务模块为目标去做隔离,尽量在每个模块的起始页面去定义这个拆分点。

使用 react-lodable,实现组件的异步加载

上面的例子你已经看到了如何去实现一个 React 组件的动态加载,它的总体思路其实主要就是三个部分:

  1. 定义一个加载器组件,在使用的地方依赖于这个加载器组件而不是原组件;
  2. 在加载器组件的执行过程中,使用 import 去动态加载真实的实现代码;
  3. 处理加载过程,和加载出错的场景,确保用户体验。