原文地址?

medium.com

在單頁面應用的時代,你可以使用React來完成幾乎所有的Web應用甚至大型的Web引用。你甚至可以使用React開發一個Facebook。

當前正如你所知,Webpack完成所有的魔法,它將會將你所有的代碼放到bundle.js這個文件中,供瀏覽器使用。這個文件將包含所有組件和所有的依賴。因此,越是龐大的應用,這個文件將會越大。當然,大文件會耗費更多的時間去下載。因此首屏幕載入會非常慢,但是事實上,很多組件都不會出現在用戶所瀏覽的那部分頁面上。

最好的實踐方案是將這個單一的bundle.js拆分成多個文件,並按需要進行載入。因此,那些不被用戶所瀏覽的頁面所包含的的組件,將不會被載入。

為了實現按需載入,是有很多的解決方案的。這裡我將分享如何使用HOC來完成這件事情。

什麼是HOC?

高階組件(HOC)是 React 中用於復用組件邏輯的一種高級技巧。HOC 自身不是 React API 的一部分,它是一種基於 React 的組合特性而形成的設計模式。

我們可以想像到HOC就是在你的組件外進行一次封裝,它會在你的組件創建時注入一些公共狀態或者修改一些組件行為。一般一個HOC就是一個非常簡單的函數,接收一個組件並返回另一個組件。然後你就能把HOC當成一個非常簡單的組件在JSX中應用。我們可以在高階組件文檔 中詳細了解高階組件。

所以,我們要做的就是定義一個HOC,並且在所有需要進行非同步載入的組件上應用。

非同步組件的HOC

讓我們先創建asyncComponent.js這樣一個文件來編寫我們的HOC代碼。同時我比較傾向在工程內建立一個叫「hoc」的目錄來存放所有的高階組件。

讓我們看看它的代碼吧:

//hoc/asyncComponent.js

import React, {Component} from react;

const asyncComponent = (importComponent) => {
return class extends Component {
state = {
component: null
}

componentDidMount() {
importComponent()
.then(cmp => {
this.setState({component: cmp.default});
});
}

render() {
const C = this.state.component;
return C ? <C {...this.props}/> : null;
}
}
};

export default asyncComponent;

如你所見,它就是返回一個函數,這個函數接收另外一個函數為參數並返回一個基類是React Component的匿名類。簡單說我們的asyncComponent是一個返回組件的函數。

我們可以看出importComponent是一個函數,它簡單的返回我們需要引入的組件,它的代碼會像下面這個樣子:

const impFn = () => {
return import(./components/Card);
}

每次importComponent函數被調用的時候,React都會嘗試引入這個組件,它將會自動載入一個包含該組件的chunk.js

使用asyncComponent

讓我們來看看,我們該如何使用這個組件和當我們使用該組件時會發生什麼。我們的例子中將在一個函數組件中使用這個高階組件。

//components/Container.js

import React from react;
import asyncComponent from ../../hoc/asyncComponent;

const AsyncButton = asyncComponent(() => {
return import(../Button);
});

const container = () => {
return (
<div>
<h1>Here goes an async loaded button component</h1>
<AsyncButton/>
</div>
);
};

export default container;

在這裡,我們定義了一個AsyncButton,而非在DOM中直接中<Button/>組件。我們已經知道我們的asyncComponent是如何定義的,我們也可以猜到AsyncButton將被作為React的組件類型來使用。但是,當這個組件被添加DOM的時候,會發生什麼呢?我們就需要到asyncComponent中尋找答案。

很顯然,當AsyncButton被掛載到DOM時(參考componentDidMount),它會調用我們的importComponent函數。在這個場景下,它會返回一個Button組件。在import操作完成前,這個地方的DOM將是空的。當缺失的組件完成了下載,它將會作為AsyncButton組件狀態添加到AsyncButton組件中,從而觸發重新渲染。現在,我們的非同步組件將會傳遞props並渲染Button組件。

好了,我們已經讓我們的Button組件在真正被需要使用的時候才被下載和渲染。

非同步組件配合路由使用

當我們的應用擁有很多容器組件(例如頁面),那就很有必要一開始只載入首先被訪問頁面,而剩下的頁面則按需載入。我們的asyncComponent可以很好的完成這件事,你只需像我們非同步載入Button組件那樣去就可以了。

這裡我們來做個簡單的例子。假設我們所有的路由被分拆到不同的文件中,在某個路由文件中只有兩個簡單的路由。Home頁面和用戶Profile頁面,其中Home頁面是首屏頁面,而用戶Profile不一定會被訪問。

import React, {Component} from react;
import {Route, Switch} from react-router-dom;

import HomePage from ./containers/HomePage;

const AsyncProfilePage = asyncComponent(() => {
return import(./containers/ProfilePage);
});

class Routes extends Component {
render() {
return (
<Switch>
<Route exact path=/ component={HomePage}/>
<Route exact path=/profile component={AsyncProfilePage}/>
</Switch>
);
}
}

export default Routes;

因此用戶的Profile頁面,將在被訪問的時候載入。

希望你能從這篇博文中學到一些好玩的東西,並且讓我們開心的編碼吧。


翻譯後記:

在非SPA的時代,也有很多非同步載入JS文件的方案,例如require.js,但是這些都要使用一些小技巧(修改DOM,增加script標籤等)。而隨著ECMAScript 6的出現,瀏覽器正全力的將ES6語法和特性直接在瀏覽器內實現。而import()函數也是ES的modules規範(動態import)的一部分,這大大的簡化了瀏覽器動態載入JS的難度。

React對大型項目也提出了自己代碼分割方案 。這篇文章只是展示下非同步載入較為淺顯的原理。

翻譯水平有限,希望多提出寶貴意見。


推薦閱讀:
相关文章