react非同步載入元件實現解析

FE_xuer發表於2019-03-04

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等等此類的方法,具體優化方式視個人偏好以及專案具體情況,也不能一概而論。
好了,感謝各位,如有錯誤,請多多指教。

相關文章