原文來自個人github: https://github.com/Vibing/blog前端
當項目越作越大時,體積過大致使加載速度過慢,性能問題直接影響用戶體驗。webpack
這時咱們會考慮將代碼拆分。git
拆分,顧名思義就是將一個大的東西拆分紅N個小的東西,用公式表示就是:Sum = n * Sub
github
代碼拆分基於動態導入
什麼是動態導入?就是我須要什麼,你給我什麼,我不須要的時候,你別給我,我嫌重。web
動態導入能夠將模塊分離成一個單獨的文件 在須要的時候加載進來。npm
對於動態導入,webpack 提供了兩個相似的技術。antd
import()
語法。 require.ensure
。從webpack 2之後,通常使用第一種。async
因爲import()
方法返回的是Promise
對象,咱們爲了能方便的返回組件,
這裏推薦使用async-loadable插件組件化
例子代碼:性能
import loadable from 'async-loadable'; import Loading from './my-loading-component'; const LoadableComponent = loadable({ loader: () => import('./my-component'), loading: status => <Loading {...status}/>, }); export default class App extends React.Component { render() { return <LoadableComponent/>; } }
代碼裏有熟悉的 import()
方法。async-loadable
使用 webpack
的動態導入,調用loadable
方法能夠方便的返回要使用的組件。
下面我將以我本人的項目經歷,來說解代碼拆分(code splitting)
當初仍是小白的我,一開始哪知道有代碼拆分這個技術啊,就一我的負責一個小項目,一開始項目不大,跑起來也是嗖嗖的,這裏先貼一下路由代碼:
import Home from './home'; import Page1 from './page1'; import Page2 from './page2'; <Route exact path="/" component={Home}/> <Route path="/page1" component={Page1}/> <Route path="/page2" component={Page2}/>
這裏沒有使用動態導入,而是直接將全部頁面靜態引入進來,而後賦到對應路由上。
這麼作的壞處就是:打包時,整個項目全部的頁面都會打包到一個文件中,隨着頁面增多,這個文件也愈來愈大,最後我看了一下,達到了近25M(我嚇得打開度娘...)。
若是用一張圖來表示的話,這張圖在適合不過了:
哈哈,整個一坨有沒有。全部路由在這一坨紅色裏,看着真特麼憋屈啊
打開度孃的我臉色漸漸有了好轉,經過搜索,看到了webpack有個code splitting
功能(代碼拆分),
前面說過,代碼拆分其實就是使用動態導入的技術實現的,那麼咱們就使用動態導入來優化一把以前的路由:
import Loadable from 'async-loadable'; import Loading from './my-loading-component'; const Home = Loadable({ loader: () => import(`./home`), loading: Loading }); const Page1 = Loadable({ loader: () => import(`./page1`), loading: Loading }); const Paeg2 = Loadable({ loader: () => import(`./page2`), loading: Loading }); <Route exact path="/" component={<Home />} /> <Route path="/page1" component={<Page1 /> } /> <Route path="/page2" component={<Page2 /> } />
咱們再也不使用 import module from 'url' 來靜態引入模塊,而是使用 loadComponent
來動態導入,它返回的是Loadable
的結果,也就是咱們想要的組件,咱們把再把組件給對應的路由,這就完成了基於路由的代碼拆分。
使用之後,鄙人懷着激動的心情開始打包項目,當我看到控制檯的打包日誌時,個人表情是這樣的:
咳咳,這種好事情固然要分享一下啦,你要的結果:
能夠看到,webpack打包時已經將以前的一個臃腫文件按路由拆分紅了三個文件,當咱們點擊一個路由時,會動態加載對應的文件。
好比我點擊home
頁面的路由時:
我再點擊page1
時:
嗯,是按照路由來拆分的代碼,完美~
這樣看來,咱們須要將以前的那張圖改爲這樣的:
看着項目加載速度變快了,內心真特麼高興
其實基於路由的代碼拆分已經能夠知足絕大多數項目了,再大的項目也能知足。
但隨着項目作的多了,慢慢的發現了一個問題:代碼浪費。
好比我要作一個Tab切換的功能,像醬紫的:
對應的代碼大概是醬紫的:
import { Tabs } from 'antd'; import TabOne from './component/tab1'; import TabTwo from './component/tab2'; import TabThree from './component/tab3'; const TabPane = Tabs.TabPane; export default class Home extends Component { render() { return ( <Tabs defaultActiveKey="1"> <TabPane tab="Tab 1" key="1"> <TabOne /> </TabPane> <TabPane tab="Tab 2" key="2"> <TabTwo /> </TabPane> <TabPane tab="Tab 3" key="3"> <TabThree /> </TabPane> </Tabs> ); } }
Tab切換,每一個前端小夥伴都作過,其實說白了,就是顯示隱藏的效果。
可是在這個頁面中,已經把每一個Tab裏的代碼都加載進來了,若是用戶只看第一個Tab,其餘Tab不點擊,就形成了代碼浪費。
如何解決這個問題呢?仍是那句話:我須要什麼,你給我什麼,我不須要的時候,你別給我,我嫌重。
咱們使用動態導入的方式改造一下代碼:
import { Tabs } from 'antd'; import Loadable from 'async-loadable'; import TabOne from './component/tab1'; import Loading from './component/loading'; const TabPane = Tabs.TabPane; const loadComponent = path => Loadable({ loader: () => import(`${path}`), loading: Loading }); const Tab2 = loadComponent('./component/tab2.tsx'); const Tab3 = loadComponent('./component/tab3.tsx'); export default class Home extends Component { render() { return ( <Tabs defaultActiveKey="1"> <TabPane tab="Tab 1" key="1"> <TabOne /> </TabPane> <TabPane tab="Tab 2" key="2"> <Tab2 /> </TabPane> <TabPane tab="Tab 3" key="3"> <Tab3 /> </TabPane> </Tabs> ); } }
一樣 咱們再也不使用import module from 'url'
的方式,而是使用 loadComponent
方法動態導入。
因爲TabOne是第一個默認顯示的,因此不必動態導入。
如今咱們來點擊Tab 2看看效果:
很是棒,正是咱們想要的。
再點擊Tab 3 :
簡直完美!😄
到目前爲止,咱們基於模塊的代碼拆分就完成了,咱們把以前的拆分圖再改一下:
看上去爽朗了不少啊!
基於路由的代碼拆分能夠很大程度上減輕代碼的臃腫,但依然會存在不會被使用的組件被import進來,致使代碼浪費。
本人認爲,既然是組件化時代,那麼就應以組件爲核心,將動態導入顆粒化到組件而不是路由,將會帶來更合理,性能更高的項目優化。