react+webpack對於react開發,相信已經是一個大眾套餐了,至於其他的parcel或者rollup或者其他一些構建框架我也沒仔細用過,也不太熟,聽說parcel上github短時間內已經上萬顆星了,很流弊的樣紙,不過這都不是我們本文重點,呵呵。本文重點是模組的非同步載入,這裡不談其他的,只談談按需載入優化。使用webpack構建的時候,我們會把公共資源打到vendor檔案中去,以便讓我們的業務檔案瘦身,但是我們的業務可能會有大量子業務,可能某些業務在某些使用者那裡永遠都不會用到,這種情況下,如果我們把所有業務程式碼一次性全部載入下來,不是太浪費太奢侈了嗎,好在webpack自身提供了require.ensure()函式,不過,這種優化方式又不在我們本次探討範圍內,本次我們來介紹一下本人心目中比較高大上的react非同步載入元件。就算不喜歡,學習一下也是極好的事情啦。
我們來看以下這段程式碼,若不採用非同步模組載入,page1和page2會合併到一個業務檔案中,我要是永遠不進入/buy路由,這不是浪費載入嗎,那page2最好還是做成非同步元件吧,那具體應該怎麼做呢?
<Route path="/" component={App}>
<IndexRoute component={page1}/>
<Route path="/buy" component={page2}/>
</Route>
複製程式碼
好咯,不就是個非同步元件嗎,那不是很簡單的嗎,不就類似下面這樣就行了的:
let page2 = ()=>{
let page2Comp = import(`lazyComp`);
return {
<div>
<page2Comp />
</div>
}
}
複製程式碼
哎呀嘛,這智商也是忒高了,以為自己多流弊啊,寥寥幾行就實現了一個非同步元件,天真!可惜就是報錯了。不知道為啥?看看import()這玩意兒返回的是啥好不好,人家返回的是個promise物件,至少得先處理一下才好吧。這就好比你請人家上你家吃酒席,你至少得先安排人位子啊,先拿條椅子佔個坑吧,等到人家來了才想起來搬凳子,任誰都不開心,掉頭就走啦。那怎麼給佔個坑呢?其實很簡單的道理,大家肯定都很熟悉的啦,請看下面一個小栗子。
class MyComp extends React.Component{
constructor(){
return {
isLoaded:false
}
}
render(){
let { isLoaded, data } = this.state;
if(!isLoaded){
return null;
}
return <Wrapper data={data} />
}
componentDidMount(){
http.getData({}).then((results)=>{
this.setState({
data:results.data,
isLoaded:true
})
})
}
}
複製程式碼
這段程式碼大家都挺熟悉了吧,資料沒返回之前,不做具體渲染,直到資料返回,才開始渲染。只不過非同步元件在這裡有所區別的是,獲取的資料就是元件本身,元件未獲取到前怎麼辦呢?簡單,用一個空元素佔個位不就OK了嘛。接下來我們來看一下webpack官網給出來的一個非同步元件實慄:
class LazilyLoad extends React.Component {
constructor() {
super(...arguments);
this.state = {
isLoaded: false,
};
}
componentDidMount() {
this._isMounted = true;
this.load();
}
componentDidUpdate(previous) {
if (this.props.modules === previous.modules) return null;
this.load();
}
componentWillUnmount() {
this._isMounted = false;
}
load() {
this.setState({
isLoaded: false,
});
const { modules } = this.props;
const keys = Object.keys(modules);
Promise.all(keys.map((key) => modules[key]()))
.then((values) => (keys.reduce((agg, key, index) => {
agg[key] = values[index];
return agg;
}, {})))
.then((result) => {
if (!this._isMounted) return null;
this.setState({ modules: result, isLoaded: true });
});
}
render() {
if (!this.state.isLoaded) return <div className="toast toast-show">
<Loading/>
</div>;
console.log("modules:",this.state.modules);
return React.Children.only(this.props.children(this.state.modules));
}
}
複製程式碼
是不是覺得跟上面非同步載入資料的栗子十分有血緣關係呢?接下來,我們具體來看一下這段程式碼,其他地方就不多說了,或許有些同學可能看不太明白render函式中的return React.Children.only(this.props.children(this.state.modules));
這一句程式碼,這種渲染方式叫做回撥渲染。稍微給大家做個分析,我們先來看看以上元件的呼叫示例程式碼:
const LazilyLoadFactory = (Component, modules) => {
console.log("LazilyLoadFactory");
return (props) => (
<LazilyLoad modules={modules}>
{(mods) => <Component {...mods} {...props} />}
</LazilyLoad>
);
};
複製程式碼
如果還是不太理解,先把上面return React.Children.only(this.props.children(this.state.modules));
這句程式碼中的幾個元素拆解一下:
- this.props.children:這個大家應該都懂啥意思了,指代呼叫元件的子元素,以上示例中,不就是指代
(mods) => <Component {...mods} {...props} />
這個函式嘛 - this.state.modules:這個就是LazilyLoad元件中傳入props的modules變數被處理成state的變數
- React.Children.only:就不必說了吧,想必都比較熟悉了
這樣稍微拆解後,是不是就很清晰了呢,回過頭來看一下我們的stateless元件LazilyLoadFactory,渲染的是LazilyLoad元件,使用回撥渲染的方式實際上以引數modules作為props入參對引數元件Component進行渲染,那就很明顯了,引數元件Component就是這個非同步元件的佔坑板凳了,我們來看看這個作為引數傳入的元件的具體程式碼:
class WrapLazyComp extends React.Component{
render(){
const Comp = this.props.Comp;
return <div>
<Comp />
</div>;
}
}
複製程式碼
好了,然後接下來就是我們的總呼叫方了
LazilyLoadFactory(WrapLazyComp,{
Comp: () => import(`實際業務模組`)
});
複製程式碼
到此為止,我們的非同步元件整個就完成了,主要就是利用import()實現對模組的非同步載入,可能有些同學對於回撥渲染可能會有些模糊,不熟悉的可能稍微需要了解了解。
個人比較喜歡這種方式進行非同步模組的載入,當然還有類似require.ensure等等此類的方法,具體優化方式視個人偏好以及專案具體情況,也不能一概而論。
好了,感謝各位,如有錯誤,請多多指教。