本文講述怎麼實現動態加載組件,並藉此闡述適配器模式。javascript
import Center from 'page/center'; import Data from 'page/data'; function App(){ return ( <Router> <Switch> <Route exact path="/" render={() => (<Redirect to="/center" />)} /> <Route path="/data" component={Data} /> <Route path="/center" component={Center} /> <Route render={() => <h1 style={{ textAlign: 'center', marginTop: '160px', color:'rgba(255, 255, 255, 0.7)' }}>頁面不見了</h1>} /> </Switch> </Router> ); }
以上是最多見的React router
。在簡單的單頁應用中,這樣寫是ok的。由於打包後的單一js文件bundle.js
也不過200k左右,gzip
以後,對加載性能並無太大的影響。
可是,當產品經歷屢次迭代後,追加的頁面致使bundle.js
的體積不斷變大。這時候,優化就變得頗有必要。java
優化使用到的一個重要理念就是——按需加載。
能夠結合例子進行理解爲:只加載當前頁面須要用到的組件。react
好比當前訪問的是/center
頁,那麼只須要加載Center
組件便可。不須要加載Data
組件。webpack
業界目前實現的方案有如下幾種:git
getComponent
方法(router4已不支持)而這些方案共通的點,就是利用webpack的code splitting
功能(webpack1使用require.ensure
,webpack2/webpack3使用import
),將代碼進行分割。github
接下來,將介紹如何用自定義高階組件實現按需加載。web
webpack將
import()
看作一個分割點並將其請求的module打包爲一個獨立的chunk。import()
以模塊名稱做爲參數名而且返回一個Promise對象。segmentfault
由於import()
返回的是Promise對象,因此不能直接給<Router/>
使用。react-router
適配器模式(Adapter):將一個類的接口轉換成客戶但願的另一個接口。Adapter模式使得本來因爲接口不兼容而不能一塊兒工做的那些類能夠一塊兒工做。異步
當前場景,須要解決的是,使用import()
異步加載組件後,如何將加載的組件交給React進行更新。
方法也很容易,就是利用state
。當異步加載好組件後,調用setState
方法,就能夠通知到。
那麼,依照這個思路,新建個高階組件,運用適配器模式
,來對import()
進行封裝。
import React from 'react'; export const asyncComponent = loadComponent => ( class AsyncComponent extends React.Component { constructor(...args){ super(...args); this.state = { Component: null, }; this.hasLoadedComponent = this.hasLoadedComponent.bind(this); } componentWillMount() { if(this.hasLoadedComponent()){ return; } loadComponent() .then(module => module.default ? module.default : module) .then(Component => { this.setState({ Component }); }) .catch(error => { /*eslint-disable*/ console.error('cannot load Component in <AsyncComponent>'); /*eslint-enable*/ throw error; }) } hasLoadedComponent() { return this.state.Component !== null; } render(){ const { Component } = this.state; return (Component) ? <Component {...this.props} /> : null; } } );
// 使用方式 const Center = asyncComponent(()=>import(/* webpackChunkName: 'pageCenter' */'page/center'));
如例子所示,新建一個asyncComponent
方法,用於接收import()
返回的Promise對象。
當componentWillMount
時(服務端渲染也有該生命週期方法),執行import()
,並將異步加載的組件,set
入state
,觸發組件從新渲染。
this.state = { Component: null, };
這裏的null
,主要用於判斷異步組件是否已經加載。
module.default ? module.default : module
這裏是爲了兼容具名
和default
兩種export
寫法。
return (Component) ? <Component {...this.props} /> : null;
這裏的null
,其實能夠用<LoadingComponent />
代替。做用是:當異步組件還沒加載好時,起到佔位的做用。
this.props
是經過AsyncComponent
組件透傳給異步組件的。
output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }
在輸出項中,增長chunkFilename
便可。
自定義高階組件的好處,是能夠按最少的改動,來優化已有的舊項目。
像上面的例子,只須要改變import
組件的方式便可。花最少的代價,就能夠獲得頁面性能的提高。
其實,react-loadable
也是按這種思路去實現的,只不過增長了不少附屬的功能點而已。