译: 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
以上就是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社区,每周定时推送优质文章。