如何使用React.lazy和Suspense进行组件延迟加载(翻译)

翻译自原文:https://medium.freecodecamp.org/how-to-use-react-lazy-and-suspense-for-components-lazy-loading-8d420ecac58

React 16.6将代码分割(code-splitting)带到了一个新的level。您现在可以在真正需要时加载组件,且无需安装其他依赖库。

什么是代码分割(code-splitting)和懒加载(lazy-loading)?

Webpack是这样定义代码分割的:

"将代码拆分为各种捆绑包,然后按需加载或并行加载的技术。" - 链接

另一种说法是:“按需加载或并行加载”是懒加载,与懒加载相反的是预加载(或立即加载)(eager-loading)。总之,无论你是否使用它,一切都会被加载。

为什么我们要使用代码分割和懒加载?

有时我们必须引入大量代码来完成某些功能,这些代码可以是来自第三方依赖库或者自行编写。总之,这些代码会影响主包的大小。

下载几MB文件对于今天的互联网速度来说是小菜一碟,但是我们仍然需要考虑网速较慢或使用移动数据的用户。
(在评论席还补充了:这里不仅仅只影响文件大小,对于添加的JavaScript每一个字节,都会增加内存和CPU使用率。- 相关链接

它们在React 16.6之前是如何实现的?

对于懒加载React组件最流行的库可能是react-loadable

⚠️需要注意的是,在服务端渲染上reactjs.org仍然建议使用可加载组件(Loadable Components)。- 相关链接

使用上,react-loadable和React的新方法非常相似,我将在下文中进行演示。

对于环境配置还需要做什么?

让我们看看reactjs.org对此有何看法:

“如果您正在使用Create React AppNext.jsGatsby或类似的工具,您将拥有一个开箱即用的Webpack配置来打包您的应用程序”
如果不是,您需要自己配置打包文件。例如,请参阅Webpack文档的“安装
入门”指南。 - reactjs.org

好的,所以需要Webpack,它处理捆绑包的动态导入。

本文将使用create-react-app脚手架生成以下演示案例,所以Webpack已经配置好了,来吧!

Demo

本演示将使用react-pdfreact-pdf是一个很棒的库,用于在浏览器、移动端和服务器上创建PDF文件。一般我们都在服务器上生成PDF,但是如果我们需要在客户端进行,则需要一个成本:包大小。

Import cost

我正在使用Visual Studio Code的Import cost插件来展示依赖库的大小。

首先,假设我们的需求是在用户点击按钮时生成PDF文件。


其次,如果这是一个很大的web app以及这个功能可能是很小一部分,也许是用户不经常使用的功能。每次页面请求都会加载整个react-pdf代码就变得无意义了。

所以,我们非常需要一个延迟加载的解决方案。

预加载 VS 懒加载 展示

一个简易的PDFPreview组件

import React from "react";
import { PDFViewer, Document, Page, Text, View } from "@react-pdf/renderer";
import pdfstyles from "./pdfStyles";

// Create Document Component
const PDFPreview = ({ title }) => (
  <PDFViewer className="viewer" style={pdfstyles.viewer}>
    <Document>
      <Page size="A4" style={pdfstyles.page}>
        <View style={pdfstyles.section}>
          <Text style={pdfstyles.title}>{title}</Text>
          <Text>This is a text in a generated PDF file.</Text>
        </View>
      </Page>
    </Document>
  </PDFViewer>
);

export default PDFPreview;

添加样式

import { StyleSheet } from "@react-pdf/renderer";
const styles = StyleSheet.create({
  viewer: {
    padding: 0
  },

  page: {
    margin: 0,
    flexDirection: "row",
    backgroundColor: "#ffffff"
  },
  title: {
    fontSize: 30,
    marginBottom: 30
  },
  section: {
    margin: 10,
    padding: 10,
    flexGrow: 1
  }
});

export default styles;

预加载

让我们看看没有延迟加载的父组件

import React, { Component } from "react";
import PDFPreview from "./PDFPreview";

class App extends Component {
  state = {
    name: "",
    showPDFPreview: false
  };

  handleClick = () => this.setState({ showPDFPreview: true });

  handleNameChange = event => this.setState({ name: event.target.value });

  render() {
    const greeting = `Hello ${this.state.name}`;

    return (
      <div className="App">
        <input
          placeholder="Enter your name"
          type="text"
          onChange={this.handleNameChange}
        />

        <button onClick={this.handleClick}>Generate PDF</button>
        {this.state.showPDFPreview && <PDFPreview title={greeting} />}
      </div>
    );
  }
}

export default App;

在浏览器中将呈现以下视图


键入值之后点击按钮生成pdf

无论我们是否点击生成PDF,与其相关的所有代码都包含在应用包内了


这是一个开发环境。在打包之后,Size将显著缩小。尽管如此,我们还是没有最佳地分割代码。

懒加载

import React, { Component, Suspense } from "react";
const LazyPDFDocument = React.lazy(() => import("./PDFPreview"));

class App extends Component {
  state = {
    name: "",
    showPDFPreview: false
  };

  handleClick = () => this.setState({ showPDFPreview: true });

  handleNameChange = event => this.setState({ name: event.target.value });

  render() {
    const greeting = `Hello ${this.state.name}`;

    return (
      <div className="App">
        <input
          placeholder="Enter your name"
          type="text"
          onChange={this.handleNameChange}
        />

        <button onClick={this.handleClick}>Generate PDF</button>
        {this.state.showPDFPreview && (
          <Suspense fallback={<div>Loading...</div>}>
            <LazyPDFDocument title={greeting} />
          </Suspense>
        )}
      </div>
    );
  }
}

export default App;

我们只做了很小的改动:
第2行替换为:

const LazyPDFDocument = React.lazy(() => import("./PDFPreview"));

让我们看看React文档对React.lazy的说法:

React.lazy必须通过调用动态的import()加载一个函数,此时会返回一个Promise,并解析(resolve)为一个带有包含React组件的默认导出的模块。 - reactjs.org

因此如果的IE中使用,需要预先使用PolyfillPromise来实现动态import

在第27行,我们使用Suspense,它必须是延迟加载组件的父级(即React.lazy加载的组件只能在<React.Suspense>组件下使用)。当showPDFPreview设置为true时,LazyPDFDocument开始加载。

在子组件被解析之前,Suspense会显示由fallback属性提供的任何内容,且fallback为必带参数

最终结果如下所示:


我们可以看到0.chunk.js的大小明显低于之前,并且按下按钮时才加载了4.chunk.js和3.chunk.js。

结论

每次我们在项目中引入新的依赖项时,我们有责任评估其成本并检查它对主应用包的影响。

如果这个功能是很少使用的,我们完全可以使用按需加载而不会牺牲用户体检。现在React.lazy和Suspense可以出色地帮我们完成这项任务。

Thank you for reading! Please share it with anyone who might find it useful and leave feedback

哦,对了,这些只对web app有必要,对React-Native当然没必要这么做啦!况且,metro尚不支持动态导入。戳metro issues #52

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容