【譯】懶加載組件

React 16.6 的新發布帶來了一些只需很小努力就能給React組件對增長了不少力量的新特性。react

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

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

代碼分割

過去幾年寫 JavaScript 應用的方式進化了。在 ES6(modules)的出現,Babel 編譯器,和其餘打包工具像是 WebPack 和Browserify,JavaScript 應用如今能夠用徹底現代化的模式寫出容易維護的東西。es6

一般,每一個模塊被導入合併在一個文件叫作 bundle,這些 bundle 在一張頁面上包括了整個APP。然而,當 APP 增加的時候,這些 bundle 尺寸開始變得愈來愈大,所以影響了頁面加載時間。github

打包工具像是 Webpack 和 Browserify 提供了代碼分割的支持,能夠在須要加載(懶加載)而不是一次性加載不一樣的 bundles 中引入分割代碼,從而提升 app 的表現。web

Dynamic Imports

代碼風格的主要方式之一是使用動態導入。動態導入做用於 import() 語法,這還不是 JavaScript 語言標準的一部分,可是一個指望不久被接受的提案。promise

調用 import() 去加載模塊依賴 JavaScript 的 Promises。所以,返回一個完整的加載的模塊或者若是模塊不存在的話就拒絕。瀏覽器

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

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

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 庫,動態建立一個分割包。app

對於 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
相關文章
相關標籤/搜索