react-loadable
最近在學習react,之前做的一個專案首屏載入速度很慢,便蒐集了一些優化方法,react-loadable
這個庫是我在研究路由元件按需載入的過程中發現的,其實react-router v4
官方也有code splitting
的相關實踐,但是在現在的webpack 3.0版本下已經不行了,因為需要使用以下相關語法
import loadSomething from `bundle-loader?lazy!./Something`
複製程式碼
有興趣的同學可以自行研究。
react-router-v4:code-splitting
後面就發現了react-loadable這個庫可以幫助我們按需載入,其實和react-router-v4官方實現的原理差不太多,基本使用方法如下:
第一步:先準備一個Loding元件,這個是官方的,你自己寫一個更好:
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
});
複製程式碼
第三步:替換我們原本的元件
<Route path="/" exact component={AsyncHome} />
複製程式碼
這樣,你就會發現只有路由匹配的時候,元件才被import進來,達到了code splitting
的效果,也就是我們常說的按需載入,程式碼分塊,而不是一開始就將全部元件載入。
可以觀察到,點選不同的路由都會載入一個chunk.js,這就是我們所分的塊。
核心:import()
不要把 import
關鍵字和import()
方法弄混了,該方法是為了進行動態載入才被引入的。
import
關鍵字的引入是靜態編譯且存在提升的,這就對我們按需載入產生了阻力(畫外音:require是可以動態載入的),所以才有了import()
,而react-loadable
便是利用了import()
來進行動態載入。
而且這個方法不能傳變數,只能使用字串和字串模板,原本想將那一堆生成元件的程式碼進行抽象,結果死活不行,才發現坑在這裡。
Loadable
react-loadable
有5k star,內部機制已經十分完善了,看現在的原始碼我肯定看不懂,於是誤打誤撞地看了其initial commit
的原始碼。
我們上面對Loadable
函式的用法是這樣的:
const AsyncHome = Loadable({
loader: () => import(`../containers/Home`),
loading: MyLoadingComponent
});
複製程式碼
接收一個配置物件為引數,第一個屬性名為loader
,是一個方法,用於動態載入我們所需要的模組,第二個引數就是我們的Loading
元件咯,在動態載入還未完成的過程中會有該元件佔位。
{
loader: () => import(`../containers/Home`),
loading: MyLoadingComponent
}
複製程式碼
這個方法的返回值是一個react component,我們Route
元件和url香匹配時,載入的就是這個component,該component通過loader方法進行非同步載入元件以及錯誤處理。其實就這麼多,也有點高階元件的意思。
然後來看看原始碼吧(原始碼引數部分使用了ts進行型別檢查)。
import React from "react";
export default function Loadable(
loader: () => Promise<React.Component>,
LoadingComponent: React.Component,
ErrorComponent?: React.Component | null,
delay?: number = 200
) {
// 有時元件載入很快(<200ms),loading 屏只在螢幕上一閃而過。
// 一些使用者研究已證實這會導致使用者花更長的時間接受內容。如果不展示任何 loading 內容,使用者會接受得更快, 所以有了delay引數。
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);
// 進行載入
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;
// 根據情況渲染Loading 所需元件 以及 錯誤元件
if (error && ErrorComponent) {
return <ErrorComponent error={error} />;
} else if (isLoading) {
return <LoadingComponent />;
} else if (Component) {
return <Component {...this.props} />;
}
return null;
}
};
}
複製程式碼
安利一下我正在練習的專案react-music
參考資料:
webpack v3 結合 react-router v4 做 dynamic import — 按需載入(懶載入)