之前寫過一篇 Vue 異步組件的文章,最近在做一個簡單項目的時候又想用到 React 異步組件,所以簡單地了解了一下使用方法,這里做下筆記。
傳統的 React 異步組件基本都靠自己實現,自己寫一個專門的 React 組件加載函數作為異步組件的實現工具,通過 import() 動態導入,實現異步加載,可以參考【翻譯】基於 Create React App路由4.0的異步組件加載(Code Splitting)這篇文章。這樣做的話還是要自己寫一個單獨的加載組件,有點麻煩。於是想找個更簡單一點的方式,沒想到真給找到了:Async React using React Router & Suspense,這篇文章講述了如何基於 React Router 4 和 React 的新特性快速實現異步組件按需加載。
2018 年 10 月 23 號,React 發布了 v16.6 版本,新版本中有個新特性叫 lazy,通過 lazy 和 Suspense 組件我們就可以實現異步組件,如果你使用的是 React v16.6 以上版本:
最簡單的實現方法:
// codes from https://react.docschina.org
import React, { lazy, Suspense } from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
從 React 中引入 lazy 方法和 Suspense 組件,然后用 lazy 方法處理我們的組件,lazy 會返回一個新的React 組件,我們可以直接在 Suspense 標簽內使用,這樣組件就會在匹配的時候才加載。
lazy 接受一個函數作為參數,函數內部使用 import() 方法異步加載組件,加載的結果返回。
Suspense 組件的 fallback 屬性是必填屬性,它接受一個組件,在內部的異步組件還未加載完成時顯示,所以我們通常傳遞一個 Loading 組件給它,如果沒有傳遞的話,就會報錯。
所以在使用 React Router 4 的時候,我們可以這樣寫:
import React, { lazy, Suspense } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';
const Index = lazy(() => import('components/Index'));
const List = lazy(() => import('components/List'));
class App extends React.Component {
render() {
return <div>
<HashRouter>
<Suspense fallback={Loading}>
<Switch>
<Route path="/index" exact component={Index}/>
<Route path="/list" exact component={List}/>
</Switch>
</Suspense>
</HashRouter>
</div>
}
}
function Loading() {
return <div>
Loading...
</div>
}
export default App;
在某些 React 版本中,lazy 函數還有 bug,會導致 React Router 的 component 屬性接受 lazy 函數返回結果時報錯:React.lazy makes Route's proptypes fail。
我也遇到了這種 bug,具體的依賴版本如下:
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-router-dom": "^4.3.1"
首次安裝依賴后就再也沒有更新過,所以小版本應該也是上面的小版本,不存在更新。
解決方法可以把 lazy 的結果放在函數的返回結果中:
import React, { lazy, Suspense } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';
const Index = lazy(() => import('components/Index'));
const List = lazy(() => import('components/List'));
class App extends React.Component {
render() {
return <div>
<HashRouter>
<Suspense fallback={Loading}>
<Switch>
<Route path="/index" exact component={props => <Index {...props}/>}/>
<Route path="/list" exact component={props => <List {...props}/>}/>
</Switch>
</Suspense>
</HashRouter>
</div>
}
}
function Loading() {
return <div>
Loading...
</div>
}
export default App;
上面代碼和之前唯一的不同就是把 lazy 返回的組件包裹在匿名函數中傳遞給 Route 組件的 component 屬性。
這樣我們的組件都會在路由匹配的時候才開始加載,Webpack 也會自動代碼進行 code split,切割成很多小塊,減小了首頁的加載時間以及單獨一個 js 文件的體積。在工作中已經實踐過了,確實好用:

如果沒有使用 React v16.6 以上版本,也可以自己實現,我們可以寫一個專門用於異步加載的函數:
function asyncComponent(importComponent) {
class AsyncComponent extends React.Component {
render() {
return this.state.component;
}
state = {
component: null
}
async componentDidMount() {
const { default: Component } = await importComponent();
this.setState({
component: <Component {...this.props}/>
});
}
}
return AsyncComponent;
}
使用的方法與 React.lazy 相同,傳入一個異步加載的函數即可,上面這個函數需要注意的地方就是 import() 進來的組件被包裹在 default 屬性里,結構時要用 const { default: Component } = ... 這種形式。
效果如下:

總的來說:
- 新版 React 使用起來更加簡便~
- 異步組件按需加載這些操作都是基於打包工具的特性,比如 Webpack 的
import~
