1. 程式人生 > >react如何實現程式碼分割,路由動態載入

react如何實現程式碼分割,路由動態載入

眾所周知,在使用webpack打包react應用時,webpack將整個應用打包成一個js檔案,當用戶訪問首屏時,會一次性載入整個js檔案,當應用的規模變得越來越龐大的時候,首屏渲染速度變慢,影響使用者體驗。

於是,webpack開發了程式碼分割的特性, 此特效能夠把程式碼分割為不同的bundle檔案,然後可以通過路由按需載入或並行載入這些檔案。

程式碼分割可以用於獲取更小的bundle,以及控制資源載入優先順序,如果使用合理,會極大影響載入時間。有三種常用的程式碼分割方法:

1、拆分入口:使用 entry 配置手動地分割程式碼。

2、防止重複:使用 CommonsChunkPlugin 去重和分離chunk。

3、動態匯入:通過模組的行內函數呼叫來分離程式碼。本文只討論動態匯入(dynamic imports)的方法。

動態匯入

當涉及到動態程式碼拆分時,webpack提供了兩個類似的技術。對於動態匯入,第一種,也是優先選擇的方式是,使用符合ECMA提案的 import() 語法。第二種,則是使用 webpack 特定的 require.ensure。本文使用第一種方式。

注意:import() 呼叫會在內部用到promise。如果在舊有版本瀏覽器中使用 import(),記得使用一個polyfill 庫(例如 es6-promise 或 promise-polyfill),來 shim Promise。

下面結合react-router 4來實現react的程式碼分割。

在React應用中實現

React應用的程式碼分割需要結合路由庫react-router使用,當前react-router的版本是V4,在使用react-router4進行程式碼分割的路上,社群已經有成熟的第三方庫進行了實現,如react-loadable。在此處將介紹如何不借助第三方庫實現程式碼分割。

此處假設你已經對react、react-router4、webpack有基本的瞭解,可以搭建簡單的開發環境。下面是本專案的基本目錄結構:

專案入口檔案src/index.js:

 

src/App.js:

在App.js中,引入react-router-dom路由模組,以及路由配置檔案routes.js,App元件主要負責通過路由配置遍歷生成一系列路由元件。

下面是路由配置src/routes.js:

routes.js中配置了路由元件需要的引數,需要注意的是在路由引數中使用了非同步元件AsyncComponent,注意這裡並沒有直接引入元件,而是傳遞一個函式引數給AsyncComponent,它將在AsyncComponent(() => import('./containers/home'))元件被建立時進行動態引入。

同時,這種傳入一個函式作為引數,而非直接傳入一個字串的寫法能夠讓webpack意識到此處需要進行程式碼分割。

使用import()需要使用Babel前處理器和動態import的語法外掛(Syntax Dynamic Import Babel Plugin)。由於 import() 會返回一個 promise,因此它可以和ES7的async函式一起使用,使用acync函式需要安裝babel-plugin-transform-runtime外掛。

安裝babel外掛:

本專案使用的其他babel外掛還有babel-core、babel-loader、babel-preset-env、babel-preset-react等,主要用於React的jsx語法編譯。

下面需要編寫babel配置檔案.babelrc,在根目錄下新建.babelrc,配置如下:

非同步元件AsyncComponent

程式碼分割的核心部分就是實現AsyncComponent,本專案的AsyncComponent放在src/components/async-component/index.js中,程式碼如下:

整個模組是一個高階元件,返回一個新的元件,傳入兩個引數,一個是需要動態載入元件的方法,第二個是動態載入時的佔位符,佔位符的預設引數為一個字串,也可以傳入一個Loading元件。

在返回的AsyncComponent元件內部,constructor中,初始化一個state為Child,值為null,並定義this.unmount =false,用於表示元件是否被解除安裝。

使用acync定義非同步方法,componentDidMount中,使用await非同步執行傳入的第一個引數,用於動態載入當前路由的元件。

注意:

當呼叫ES6模組的import()方法(引入模組)時,必須指向模組的.default值,因為它才是promise 被處理後返回的實際的module物件。

故此處使用ES6的物件解構獲取到模組的default並賦值到Child上。

然後判斷元件被解除安裝的狀態,被解除安裝即返回。

下面將Child設定到state上。

在render方法中,從state中獲取Child,然後使用三元運算子判斷Child是否存在,存在則渲染Child元件,並傳入this.props,否則渲染佔位符。

元件componentWillUnmount時,設定this.unmout為true。

測試

現在開始編寫一些簡單的業務元件用於測試,在containers中新建兩個資料夾home和detail,在兩個資料夾下編寫index.js作為兩個路由元件。程式碼如下:

containers/home/index.js:

containers/detail/index.js:

在根目錄下package.json配置啟動指令碼:

然後執行npm start啟動專案:

開啟瀏覽器訪問localhost:8080

檢視右側network面板,可以看到頁面先載入了main.js和0.js,點選詳情按鈕跳轉到http://localhost:8080/detail

隨後載入了1.js,這樣就實現了程式碼分割,每個路由都是動態載入的。在大型React應用中,將bundle進行細粒度的拆分,可以極大提升首屏渲染速度,提升使用者體驗。