美文网首页react
如何使用React.lazy和Suspense进行组件延迟加载(

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

作者: 罗坤_23333 | 来源:发表于2019-02-19 15:32 被阅读1420次

    翻译自原文: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

    相关文章

      网友评论

        本文标题:如何使用React.lazy和Suspense进行组件延迟加载(

        本文链接:https://www.haomeiwen.com/subject/xmexyqtx.html