【譯】懶載入元件

toddmark發表於2019-03-18

React 16.6 的新發布帶來了一些只需很小努力就能給React元件對增加了很多力量的新特性。

其中有兩個是 React.SuspenseReact.lazy(), 這個很容易用在程式碼分割和懶載入上。

這篇文章關注在如何在 React 應用中使用兩個新特性和他們給 React 開發者帶來的新的潛力。

程式碼分割

過去幾年寫 JavaScript 應用的方式進化了。在 ES6(modules)的出現,Babel 編譯器,和其他打包工具像是 WebPack 和Browserify,JavaScript 應用現在可以用完全現代化的模式寫出容易維護的東西。

通常,每個模組被匯入合併在一個檔案叫做 bundle,這些 bundle 在一張頁面上包括了整個APP。然而,當 APP 增長的時候,這些 bundle 尺寸開始變得越來越大,因此影響了頁面載入時間。

打包工具像是 Webpack 和 Browserify 提供了程式碼分割的支援,可以在需要載入(懶載入)而不是一次性載入不同的 bundles 中引入分割程式碼,從而提高 app 的表現。

Dynamic Imports

程式碼風格的主要方式之一是使用動態匯入。動態匯入作用於 import() 語法,這還不是 JavaScript 語言標準的一部分,但是一個期望不久被接受的提案。

呼叫 import() 去載入模組依賴 JavaScript 的 Promises。因此,返回一個完整的載入的模組或者如果模組不存在的話就拒絕。

對於老的瀏覽器,es6-promise 補充應該用來補充 Promise

這兒有一個用 Webpack 打包的app的內容,看起來是動態匯入模組:

import(/* webpackChunkName: "moment" */ 'moment')
.then(({default: moment}) => {
  const tommorrow =moment().startOf('day').add(1, 'day');
  return tomorrow.format('LLL');
})
.catch(error => console.error("..."))
複製程式碼

當 Webpack 看到這樣的語法,它會為 moment 庫,動態建立一個分割包。

對於 React 應用,如果使用 create-react-app 或者 Next.js,程式碼分割在 import()中悄悄產生。

然而,如果自定義了 Webpack的設定,你需要檢查 Webpack 指導。對於 Babel 轉化,你需要 yarnpkg.com/en/package/… 外掛,允許 Babel 正確解析 import()

React 元件的程式碼分割

已經有幾種技術應用於 React 元件的程式碼分割上。常見的實現是動態 import()在應用中懶載入路由元件——這個通常是作為基於路由程式碼分割的元件。

然而,這裡有個叫 React-loadable 的非常流行的包用於 React 元件的程式碼分割。它提供一個高階函式用 promise 來載入 React 元件,實現動態 import() 語法。

考慮下面叫做 MyComponent 的 React 元件:

import OtherComponent from './OtherComponent';

export defautl function MyComponent() {
  return (
    <div>
      <h1>My Component</h1>
      <OtherComponent />
    </div>
  )
}
複製程式碼

這裡,OtherComponent 是不會請求直到MyComponent開始渲染。然而,因為我們靜態匯入了 OtherComponent,它會和 MyComponent 一起打包。

我們可以使用 react-loadable 去延遲載入 OtherComponent,直到我們渲染MyComponent,從而程式碼分割成幾個包。這裡有個用 react-loadable 懶載入的OtherComponent

impoort Loadable from 'react-loadable';

const LoadableOtherComponent = loadable({
  loader: () => import('./OtherComponent'),
  loading: () => <div>Loading...</div>
});

export default function MyComponent() {
  return (
    <div>
      <h1>My Component</h1>
      <LoadableOtherComponent/>
    </div>
  )
}
複製程式碼

在這裡能看到在選擇物件中,元件被動態 import() 語法匯入,賦值給 loader 屬性。

React-loadable 也是用了 loading 屬性去具體指出當等待真正元件載入時,將會渲染的回撥元件。

你可以在這篇文件中瞭解你能通過 react-loadable 實現什麼。

使用 Suspense 和 React.lazy()

在 React 16.6 中,支援基礎元件的程式碼分割和懶載入已經通過 React.lazy()React.Suspense 新增。

React.lazy() 和 Suspense 還沒有支援服務端。服務端的程式碼分割,仍然使用 React-Loadable。

React.lazy()

React.lazy() 很容易建立一個使用動態 import 的元件,而且像常規元件一樣渲染。當元件被渲染時,它會自動打包包含這個載入的元件。

當呼叫 import() 載入元件,React.lazy()` 使用一個必須返回一個 promise 的引數的方法。這個預設匯出包含 React 元件返回的 Promise 處理了模組。

當使用 React.lazy() 時,看起來像:

// 不使用 React.lazy()

import OtherComponent from './OtherComponent';

const MyComponent = () => {
  <div>
    <OtherComponent />
  </div>
};

// 使用 React.lazy()

const OtherComponent = React.lazy(() => import('./OtherComponent'));

const MyComponent = () => {
  <div>
    <OtherComonment />
  </div>
}
複製程式碼

Suspense

一個使用 React.lazy() 的元件只會在它需要的時候被載入。

因此,這裡需要展示一些佔位符內容的格式,當懶載入元件正在被載入的時候,比如用一個載入指示器。 這就是 React.Suspense 所建立的。

React.Suspense 是一個包裹了懶載入元件的元件。你可以在不同的層級上使用一個 Suspense 元件包裹多個懶載入元件。

當所有懶載入元件載入後,這個 Suspense 元件使用 fallback 屬性可以接受任何你想渲染的元件作為一個佔位符。

import React, { Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./OtherComponent'));

const MyComponent = () => {
  <div>
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  </div>
}
複製程式碼

如果元件懶載入失敗,在懶載入之上放置明顯的錯誤邊界來展示不錯的使用者體驗。

我在 CodeSandbox 上已經建立了一個很簡單的例子來演示使用 React.lazy()Suspense 作為懶載入元件。

這裡有個微型的app程式碼:

import React, { Suspense } from "react";
import Loader from "./components/Loader";
import Header from "./components/Header";
import ErrorBoundary from "./components/ErrorBoundary";

const Calendar = React.lazy(() => {
  return new Promise(resolve => setTimeout(resolve, 5 * 1000)).then(
    () =>
      Math.floor(Math.random() * 10) >= 4
        ? import("./components/Calendar")
        : Promise.reject(new Error())
  );
});

export default function CalendarComponent() {
  return (
    <div>
      <ErrorBoundary>
        <Header>Calendar</Header>

        <Suspense fallback={<Loader />}>
          <Calendar />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}
複製程式碼

這裡,一個很簡單的 Loader 元件在懶載入 Calendar 元件中被建立用作回撥內容。當懶元件 Calendar 載入失敗,一個邊界提示被建立來展示友好的錯誤。

我這裡包裹了懶載入日曆來模擬5秒延時。為了增加 Calendar 元件載入失敗的概率,我也使用一個條件匯入 Calendar 元件,或者返回一個promise的rejects。

const Calendar = React.lazy(() => {
  return new Promise(resolve => setTimeout(resolve, 5 * 1000)).then(
    () => Math.floor(Math.random() * 10 )>= 4 ?
     import("./components/Calendar"):
     Promise.reject(new Error())
  )
})
複製程式碼

下面的截圖展示了當渲染的時候元件看起來的示例。

test

命名匯出

如果你希望使用一個命名的匯出元件,那麼你需要再次匯出他們,作為在獨立的中間模組中的預設匯出。

如果你有一個 OtherComponent 作為命名匯出模組,你希望使用 React.lazy() 來載入 OtherComponent,那麼你需要建立一箇中間模組來再次匯出 OtherComponent 作為 預設模組。

Component.js

export const FirstComponent = () => {/* 元件邏輯 */}

export const SecondComponent = () => {/* 元件邏輯 */}

export const OtherComponent = () => {/* 元件邏輯 */}
複製程式碼

OtherComponent.js

export { OtherComponet as defatul } from './Components';
複製程式碼

這時候你可以使用 React.lazy() 去載入 OtherComponent 從中間模組。

懶載入路由

使用 React.lazy()Suspense,現在很容易處理基於路由的程式碼分割而不使用其他外部依賴。你可以簡單地轉化應用的路由組建成為懶載入元件,包裹所有的路由通過 Suspense 元件。

下面的程式碼使用 React Router 展示了基於路由的程式碼分割:

import React, { Suspense } from 'react';
import { Router } from '@reach/router';
import Loading from './Loading';

const Home = React.lazy(() => import('./Home'));
const Dashboard = React.lazy(() => import('./Dashboard'));
const Overview = React.lazy(() => import('./Overview'));
const History = React.lazy(() => import('./History'));
const NotFound = React.lazy(() => import('./NotFound'));

function App() {
  return (
    <div>
      <Suspense fallback={<Loading />}>
        <Router>
          <Home path="/" />
          <Dashboard path="dashboard">
            <Overview path="/" />
            <History path="/history" />
          </Dashboard>
          <NotFound default />
        </Router>
      </Suspense>
    </div>
  )
}
複製程式碼

總結

With the new React.lazy()React.Suspense, code-splitting and lazy-loading React components has been made very easy.

現在開始從 React 16.6享受吧。

pic

相關文章