譯: 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 />
所消耗的時間記錄。網絡
展現StockTable
一共耗時2470ms(模擬Fast3G網絡環境與4核普通CPU)
經過下圖,能夠了解到在向瀏覽器傳輸壓縮後的125K文件中都包含了什麼
如咱們所預期,頁面加載了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()
, 咱們在代碼中作了兩處變動。
首先,將靜態引用組件的代碼import StockChart from "./StockChart"
替換爲調用React.lazy()
,在lazy()
傳入一個匿名函數做爲參數,在函數中動態引入StockChart
組件。這樣在咱們渲染這個組件前,瀏覽器將不會下載./StockChart.js
文件和它的依賴。
若是React要渲染<StockChart />
組件時,組件依賴的代碼還沒下載好,會怎樣呢? 這就是爲何咱們添加了<React.Suspense/>
。在代碼未下載好前,它將會渲染fallback
props屬性傳入的值,當所有子節點依賴的代碼都準備好後,纔會去渲染子節點內容。
如今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>
);
}
}
複製代碼
在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社區,每週定時推送優質文章。