React 非同步元件

Russ_Zhong發表於2019-01-12

之前寫過一篇 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 檔案的體積。在工作中已經實踐過了,確實好用:

pic

如果沒有使用 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 } = ... 這種形式。

效果如下:

pic

總的來說:

  • 新版 React 使用起來更加簡便~
  • 非同步元件按需載入這些操作都是基於打包工具的特性,比如 Webpack 的 import

相關文章