隨著單頁應用(SPA)概念的日趨火熱,React框架在設計和實踐中同樣也圍繞著SPA的概念來打造自己的技術棧體系,其中路由模塊便是非常重要的一個組成部分。它承載著應用功能分區,復雜模塊組織,數據傳遞,應用狀態維護等諸多功能,如何結合好React框架的技術棧特性來進行路由模塊設計就顯得尤為重要,本文則以探索React動態路由設計最佳實踐作為切入點,分享下在實際項目開發中的心得與體會。
為什么需要做動態路由
動態路由:對于大型應用來說,一個首當其沖的問題就是所需加載的JavaScript的大小。程序應當只加載當前渲染頁所需的JavaScript。有些開發者將這種方式稱之為"代碼分拆(code-splitting)"—將所有的代碼分拆成多個小包,在用戶瀏覽過程中按需加載。
1、首屏加載效率
隨著項目的業務需求持續添加,react中的代碼復雜度將面臨著持續上升的問題,同時由于react中的jsx和es6語法的文件在實際生產環境中,也會被babel-js重新編譯成瀏覽器所支持的基于ES5的語法模塊,各個模塊打體積將會變得非常的臃腫不堪,直接影響到頁面加載的等待時常。以下圖為例,如果不做處理,我們的業務模塊通常體積會達到兆級,這對首屏加載速率和用戶體驗的影響無疑是巨大的。
2、降低模塊間的功能影響
react中的jsx無疑是一個很方便的設計,能讓開發者像寫html一樣來書寫虛擬dom,但是它同樣也貫徹執行著"allinjs"的理念,最終構建完成后所有的業務代碼都將打包到1-2個bundle文件中,這就等于將所有的功能模塊都集中到了一個物理文件中,如果遇到業務處理的復雜性,接口層變更,異常處理出錯等諸多代碼健壯性問題時,一個子模塊出現了錯誤,就很有可能導致用戶界面整體性出錯從而無法使用的風險。
3、符合二八定律
通常在一個應用中,最重要和高頻訪的功能模塊只占其中一小部分,約20%,其余80%盡管是多數,卻是次要的。以后臺系統為例,普通業務人員通常使用的高頻模塊只有3-5個,但是業務系統通常會有各式各樣的權限設計,不同的權限映射著能訪問的路由模塊也不盡相同,雖然我們可以在用戶的數據訪問和路由地址上做攔截限制,但是同樣也需要對其能訪問的模塊資源進行限制,才能做到真正的按需加載,隨取隨用。
4、工具體系支撐
無論是react-router還是對應搭配的構建工具webpack,其中都有針對動態路由部分的設計與優化,使用好了往往能起到事半功倍的效果。
簡化版實現:bundle-loaderbundle-loader是webpack官方出品與維護的一個loader,主要用來處理異步模塊的加載,將簡單的頁面模塊轉成異步模塊,非常方便。
1.改造前頁面
2.在webpack.config.js中增加rules
3.在工程中使用帶xxx.bunlde.js結尾的類型文件時,就會被bundle-loader識別并做編譯處理
4.創建LazyBundle.js文件,這個文件會用來調用被bundle-loader處理后的組件
5.對我們需要異步加載的組件函數進行二次封裝
注:react-router3和4由于是不兼容升級,所以處理動態路由的方法也略有不同,在此列出了兩種版本下的處理方式可供參考
6.改造后頁面
完成構建后我們就可以從瀏覽器中看到,我們定制后的模塊已經被能被支持異步加載了同時在webpack構建中也能清晰地看到多了一個chunk:
高階版實現:dynamic-imports
dynamic-imports是webpack在升級到2版本以后,對js的模塊處理進行了增強的,其中就有對require.ensure的改進,基于原生的Promise對象進行了重新實現,采用了import()作為資源加載方法,將其看做一個分割點并將其請求的module打包為一個獨立的chunk。import()以模塊名稱作為參數并且返回一個Promise對象,具體介紹可以參考筆者之前寫過的翻譯文章Webpack2升級指南和特性摘要,具體使用比對如下:
結合import的高級特性,我們就可以省去bundle-loader的處理方式,直接在原生模塊上進行動態路由處理,具體設計實現如下:
1.封裝一個高階組件,用來實現將普通的組件轉換成動態組件
2.對我們需要用到的普通組件進行引入和包裝處理
利用weback3中的MagicComments對生成的chunk指定chunkName完成構建后我們就可以從瀏覽器中看到,我們定制后的模塊也和之前一樣,被能被支持異步加載了
同時在webpack構建界面中的能看到多了一個chunk,并且chunkName就是我們自定義的名稱,對于定位分析一些模塊問題時會非常管用。從中我們也不難發現,相對于bundle-loader,dynamic-imports+AsyncComponent高階組件的方式更為簡單靈活,同時對于現有的代碼改動也較小,故作為在實際開發中的首選方案使用,同時我們也推薦一個非常不錯的webpack的chunk分析工具webpack-bundle-analyzer,方便查看每個異步路由中的構建的具體模塊內容。
Onemorething:路由模塊的組織
react-router功能強大,上手簡單,作為官方唯一指定的路由框架已經成為了react應用開發中必備的部分,但是由于react天生組件化的原因,意味著react-router的配置文件中在實際使用中,會難免出現如下不佳場景:
1、路由配置入口文件持續臃腫,文件越引越多
2、路由配置會隨著業務嵌套越來越深,團隊協作開發時極易產生沖突
3、非jsx寫法,模塊清晰簡單,但是會導致路由模塊和業務模塊耦合,不利于集中管理,同時無法明確表達出母子路由的嵌套關系,參見huge-apps
問題來了:如何既保證路由模塊的清晰簡單,又能集中管理維護,還能支持嵌套定義和動態加載?
借鑒pythonflask中的blueprint設計思路,重新實現路由模塊的劃分
經過前面的分析,我們不難發現react-router的路由配置模塊會隨著業務的深入變得越來越臃腫,其根本原因在于我們將所有的資源和配置信息都寫在了一個文件中,這和軟件設計中提倡的清晰一單一,低耦合高內聚等指導原則是背道而馳的,為此我們針對路由模塊的劃分這塊進行了重構,改進方式如下:
1、拆分routes.js入口文件
將路由模塊的整體由一個routes.js文件拆成若干個彼此間互相獨立的子路由模塊文件模塊的拆分原則可以和業務功能劃分一一對應,逐步減少主配置中的內容耦合。
2、在模塊的入口文件index.js中完成對各個子模塊的引入,如下所示:
3、在子路由模塊中完成對應具體業務模塊的加載,支持同時混合使用同步和異步組件的管理方式
4、優勢小結:這樣重構的好處是即使未來隨著業務的深入,對應的開發人員也只需要維護自身負責的子路由模塊,再在根路由下進行注冊即可使用,并且由于子路由模塊都從物理文件上進行了隔離,也能最大程度地減少協作沖突,同時,因為維持了jsx的描述型結構,路由的嵌套關系和集中維護等優點依舊能沿用。總結
本文從react-router的動態路由實踐著手,整合了webpack的bundle-loader,dynamic-imports和高階組件等實踐的明細介紹,附帶介紹了改進路由模塊的組織方式,以此作為react-router深入實踐的經驗總結,希望能對各位讀者在實際項目開發中有所幫助。
參考文獻
Webpack3官方文檔
React-Router官方文檔
基于Webpack2的React組件懶加載
React-router4按需加載的實現方式及原理
ReactRouter最新指南與異步加載實踐
希望我的回答可以對你有所幫助!