[譯] React 16.6 懶加載(與預加載)組件

譯: Lazy loading (and preloading) components in React 16.6javascript

React 16.6添加了一個新的特性: React.lazy(), 它可讓代碼分割(code splitting)更加容易。html

接下來經過一個股票App Demo, 來學習如何使用React.lazy這個新特性並瞭解爲何要使用它。java

咱們建立了一個股票Web App,App展現了一些股票的列表,點擊其中的一個股票,它會展現出最近這隻股票的走勢圖。react

try it

以上就是App的所有功能了。 你能夠在Github Repo閱讀項目源碼(也能夠經過PR,查看每一次提交的項目變動和可運行版本。)webpack

在本文,咱們只關心App.js這個文件內的代碼邏輯。git

import React from "react";
import StockTable from "./StockTable";

import StockChart from "./StockChart";

class App extends React.Component {
  state = {
    selectedStock: null
  };
  render() {
    const { stocks } = this.props;
    const { selectedStock } = this.state;
    return (
      <React.Fragment>
        <StockTable
          stocks={stocks}
          onSelect={selectedStock => this.setState({ selectedStock })}
        />
        {selectedStock && (
          <StockChart
            stock={selectedStock}
            onClose={() => this.setState({ selectedStock: false })}
          />
        )}
      </React.Fragment>
    );
  }
}

export default App;
複製代碼

App組件獲取股票列表的數據而且展現了<StockTable/>組件。當其中一個股票被點擊選中時, App將會展現那隻股票的走勢圖<StockChart>github

這裏有什麼問題呢?web

咱們想要App儘量快速加載與展現<StockTable />, 但App卻要等待瀏覽器下載(解壓, 分析, 編譯, 執行等)StockChart的代碼。瀏覽器

經過Chrome DevTools能夠看到展現<StockTable />所消耗的時間記錄。網絡

Trace without lazy loading

展現StockTable一共耗時2470ms(模擬Fast3G網絡環境與4核普通CPU)

經過下圖,能夠了解到在向瀏覽器傳輸壓縮後的125K文件中都包含了什麼

Webpack Budle Analyzer Report

如咱們所預期,頁面加載了react, react-dom 和一些react的依賴包,但頁面也同時加載了組件依賴的moment, lodash, victory的依賴。展現<StockTable />是不須要這些依賴的。

那如何加載 <StockChart />的依賴纔不會影響的加載速度呢?

懶加載組件

經過使用webpack的'dynamic import', 咱們能夠將打包的代碼拆分紅兩部分,main文件裏包含了須要展現<StockTable>的代碼及依賴。另外一個文件包含了展現<StockChart />的代碼及依賴包。

dynamic import技術是十分有用的,因此React16.6版本新添加了一個API - React.lazy(), 能夠更便利地去異步引用React組件。

爲了在App.js中使用React.lazy(), 咱們在代碼中作了兩處變動。

Diff

首先,將靜態引用組件的代碼import StockChart from "./StockChart"替換爲調用React.lazy(),在lazy()傳入一個匿名函數做爲參數,在函數中動態引入StockChart組件。這樣在咱們渲染這個組件前,瀏覽器將不會下載./StockChart.js文件和它的依賴。

若是React要渲染<StockChart />組件時,組件依賴的代碼還沒下載好,會怎樣呢? 這就是爲何咱們添加了<React.Suspense/>。在代碼未下載好前,它將會渲染fallbackprops屬性傳入的值,當所有子節點依賴的代碼都準備好後,纔會去渲染子節點內容。

如今App將會被打包成兩個文件。

main.js文件只有36kb,包含<StockChart />及其依賴的代碼文件89KB。

在優化後, 以下圖,瀏覽器展現了<StockTable />須要消耗的時間。

瀏覽器用了760ms去下載main.js(之前是1250ms)和執行腳本消耗61ms(之前是487ms). 展現<StockTable />只用了1546ms(之前是2460ms)。

預加載-懶加載組件

如今咱們已經讓App加載的更快了。但還有另外一個問題。

用戶在第一次點擊Item時,會展現"Loading...."的回退方案的組件。這是由於App須要等待瀏覽器加載好<StockChart />的代碼。

若是咱們想避免展現"Loading...."這樣的loading狀態,咱們須要在用戶點擊以前就加載好代碼。

一個簡單實現預加載代碼的方式就是提早調用React.lazy()

const stockChartPromise = import("./StockChart");
const StockChart = React.lazy(() => stockChartPromise);
複製代碼

當咱們調用dynamic imoprt時,組件就會開始加載,而且它不會阻塞<StockTable />組件的加載。

看下App加載的記錄以及與未修改版本的對比

當用戶在1s內點擊Item時,纔會看到「Loading...」

你也可使用你本身的方式去優化lazy函數,讓預加載組件更加通用方便。

function lazyWithPreload(factory) {
  const Component = React.lazy(factory);
  Component.preload = factory;
  return Component;
}

const StockChart = lazyWithPreload(() => import("./StockChart"));

// somewhere in your component 
...
  handleYouMayNeedToRenderStockChartSoonEvent() {
    StockChart.preload();
  }
...
複製代碼

預渲染組件

以上功能已經知足Demo App的使用了。但對於更大型的項目,在懶加載組件被加載以前,組件可能還會有其餘懶加載組件的代碼或數據,因此用戶仍是須要時間等待組件加載。

那另一種預加載組件的方式就是提早渲染它。在頁面中渲染組件,可是並不在頁面中展現,也就是隱藏渲染。

class App extends React.Component {
  state = {
    selectedStock: null
  };
  render() {
    const { stocks } = this.props;
    const { selectedStock } = this.state;
    return (
      <React.Suspense fallback={<div>Loading...</div>}>
        <StockTable
          stocks={stocks}
          onSelect={selectedStock => this.setState({ selectedStock })}
        />
        {selectedStock && (
          <StockChart
            stock={selectedStock}
            onClose={() => this.setState({ selectedStock: false })}
          />
        )}
        {/* Preload <StockChart/> */}
        <React.Suspense fallback={null}>
          <div hidden={true}>
            <StockChart stock={stocks[0]} />
          </div>
        </React.Suspense>
      </React.Suspense>
    );
  }
}
複製代碼

diff

在App第一次渲染後,React將會加載<Stockchart />並嘗試去渲染組件,因此組件須要的依賴或代碼也會被加載。

咱們將懶加載組件包裹在一個隱藏的div中, 在加載以後頁面不會展現任何東西。而且還用了React.suspense包裹住這個div,且其fallback值爲null,這樣它在加載時也不會被展現出來。

注:hidden屬性一般代表該節點是不相關的,瀏覽器將不會渲染具備這個屬性的元素。而React並不會對這個屬性作任何特殊處理(但在將來的版本中可能會較低優先級處理被隱藏的組件)

還有什麼呢?

最後一個方法在不少場景下是可用的,但它仍有一些問題。

第一, 對於被隱藏渲染的懶加載組件, hidden屬性並非徹底有效的。舉個例子,使用portal的懶加載組件將不會被隱藏(可使用portal來實現隱藏功能且不用一個額外的div,但這只是一個hack方式,在將來它將不會被使用)。

第二,雖然Dom節點已經被隱藏,但仍是添加了額外的Dom節點,這可能會成爲一個性能問題。

一個更好的辦法就是,通知react去渲染這個懶加載組件,但在加載後並不把它添加到Dom樹中。但據我所瞭解,在當前版本的React中是沒法實現的。

另外咱們能作的改進就是重複使用咱們預渲染時準備的Dom,這樣當真正要去渲染圖表組件時,React就無需再建立一遍它。若是用戶將會點擊某隻股票,咱們能夠在用戶點擊前用正確的數據渲染它(就像這樣)

這就是文章的所有了,感謝閱讀。


《IVWEB 技術週刊》 震撼上線了,關注公衆號:IVWEB社區,每週定時推送優質文章。

相關文章
相關標籤/搜索