react-loadable
最近在學(xué)習(xí)react,之前做的一個(gè)項(xiàng)目首屏加載速度很慢姆打,便搜集了一些優(yōu)化方法,react-loadable
這個(gè)庫(kù)是我在研究路由組件按需加載的過程中發(fā)現(xiàn)的,其實(shí)react-router v4
官方也有code splitting
的相關(guān)實(shí)踐,但是在現(xiàn)在的webpack 3.0版本下已經(jīng)不行了蜡镶,因?yàn)樾枰褂靡韵孪嚓P(guān)語法
import loadSomething from 'bundle-loader?lazy!./Something'
有興趣的同學(xué)可以自行研究。
react-router-v4:code-splitting
后面就發(fā)現(xiàn)了react-loadable這個(gè)庫(kù)可以幫助我們按需加載恤筛,其實(shí)和react-router-v4官方實(shí)現(xiàn)的原理差不太多官还,基本使用方法如下:
第一步:先準(zhǔn)備一個(gè)Loding組件,這個(gè)是官方的,你自己寫一個(gè)更好:
const MyLoadingComponent = ({ isLoading, error }) => {
// Handle the loading state
if (isLoading) {
return <div>Loading...</div>;
}
// Handle the error state
else if (error) {
return <div>Sorry, there was a problem loading the page.</div>;
}
else {
return null;
}
};
第二步:引入react-loadable
import Loadable from 'react-loadable';
const AsyncHome = Loadable({
loader: () => import('../containers/Home'),
loading: MyLoadingComponent
});
第三步:替換我們?cè)镜慕M件
<Route path="/" exact component={AsyncHome} />
這樣叹俏,你就會(huì)發(fā)現(xiàn)只有路由匹配的時(shí)候妻枕,組件才被import進(jìn)來僻族,達(dá)到了code splitting
的效果粘驰,也就是我們常說的按需加載屡谐,代碼分塊,而不是一開始就將全部組件加載蝌数。
可以觀察到愕掏,點(diǎn)擊不同的路由都會(huì)加載一個(gè)chunk.js,這就是我們所分的塊顶伞。
核心:import()
不要把 import
關(guān)鍵字和import()
方法弄混了饵撑,該方法是為了進(jìn)行動(dòng)態(tài)加載才被引入的。
import
關(guān)鍵字的引入是靜態(tài)編譯且存在提升的唆貌,這就對(duì)我們按需加載產(chǎn)生了阻力(畫外音:require是可以動(dòng)態(tài)加載的)滑潘,所以才有了import()
,而react-loadable
便是利用了import()
來進(jìn)行動(dòng)態(tài)加載锨咙。
而且這個(gè)方法不能傳變量语卤,只能使用字符串和字符串模板,原本想將那一堆生成組件的代碼進(jìn)行抽象酪刀,結(jié)果死活不行粹舵,才發(fā)現(xiàn)坑在這里。
Loadable
react-loadable
有5k star,內(nèi)部機(jī)制已經(jīng)十分完善了骂倘,看現(xiàn)在的源碼我肯定看不懂眼滤,于是誤打誤撞地看了其initial commit
的源碼。
我們上面對(duì)Loadable
函數(shù)的用法是這樣的:
const AsyncHome = Loadable({
loader: () => import('../containers/Home'),
loading: MyLoadingComponent
});
接收一個(gè)配置對(duì)象為參數(shù),第一個(gè)屬性名為loader
历涝,是一個(gè)方法诅需,用于動(dòng)態(tài)加載我們所需要的模塊,第二個(gè)參數(shù)就是我們的Loading
組件咯荧库,在動(dòng)態(tài)加載還未完成的過程中會(huì)有該組件占位诱担。
{
loader: () => import('../containers/Home'),
loading: MyLoadingComponent
}
這個(gè)方法的返回值是一個(gè)react component,我們Route
組件和url香匹配時(shí),加載的就是這個(gè)component电爹,該component通過loader方法進(jìn)行異步加載組件以及錯(cuò)誤處理蔫仙。其實(shí)就這么多,也有點(diǎn)高階組件的意思丐箩。
然后來看看源碼吧(源碼參數(shù)部分使用了ts進(jìn)行類型檢查)摇邦。
import React from "react";
export default function Loadable(
loader: () => Promise<React.Component>,
LoadingComponent: React.Component,
ErrorComponent?: React.Component | null,
delay?: number = 200
) {
// 有時(shí)組件加載很快(<200ms),loading 屏只在屏幕上一閃而過屎勘。
// 一些用戶研究已證實(shí)這會(huì)導(dǎo)致用戶花更長(zhǎng)的時(shí)間接受內(nèi)容施籍。如果不展示任何 loading 內(nèi)容,用戶會(huì)接受得更快, 所以有了delay參數(shù)概漱。
let prevLoadedComponent = null;
return class Loadable extends React.Component {
state = {
isLoading: false,
error: null,
Component: prevLoadedComponent
};
componentWillMount() {
if (!this.state.Component) {
this.loadComponent();
}
}
loadComponent() {
// Loading占位
this._timeoutId = setTimeout(() => {
this._timeoutId = null;
this.setState({ isLoading: true });
}, this.props.delay);
// 進(jìn)行加載
loader()
.then(Component => {
this.clearTimeout();
prevLoadedComponent = Component;
this.setState({
isLoading: false,
Component
});
})
.catch(error => {
this.clearTimeout();
this.setState({
isLoading: false,
error
});
});
}
clearTimeout() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
}
}
render() {
let { error, isLoading, Component } = this.state;
// 根據(jù)情況渲染Loading 所需組件 以及 錯(cuò)誤組件
if (error && ErrorComponent) {
return <ErrorComponent error={error} />;
} else if (isLoading) {
return <LoadingComponent />;
} else if (Component) {
return <Component {...this.props} />;
}
return null;
}
};
}
安利一下我正在練習(xí)的項(xiàng)目react-music
參考資料:
webpack v3 結(jié)合 react-router v4 做 dynamic import — 按需加載(懶加載)