之前寫過一篇 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
~