React 16.6 的新發布帶來了一些只需很小努力就能給React組件對增長了不少力量的新特性。react
其中有兩個是 React.Suspense
和 React.lazy()
, 這個很容易用在代碼分割和懶加載上。webpack
這篇文章關注在如何在 React 應用中使用兩個新特性和他們給 React 開發者帶來的新的潛力。git
過去幾年寫 JavaScript 應用的方式進化了。在 ES6(modules)的出現,Babel 編譯器,和其餘打包工具像是 WebPack 和Browserify,JavaScript 應用如今能夠用徹底現代化的模式寫出容易維護的東西。es6
一般,每一個模塊被導入合併在一個文件叫作 bundle,這些 bundle 在一張頁面上包括了整個APP。然而,當 APP 增加的時候,這些 bundle 尺寸開始變得愈來愈大,所以影響了頁面加載時間。github
打包工具像是 Webpack 和 Browserify 提供了代碼分割的支持,能夠在須要加載(懶加載)而不是一次性加載不一樣的 bundles 中引入分割代碼,從而提升 app 的表現。web
代碼風格的主要方式之一是使用動態導入。動態導入做用於 import()
語法,這還不是 JavaScript 語言標準的一部分,可是一個指望不久被接受的提案。promise
調用 import()
去加載模塊依賴 JavaScript 的 Promises。所以,返回一個完整的加載的模塊或者若是模塊不存在的話就拒絕。瀏覽器
這兒有一個用 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 組件的代碼分割上。常見的實現是動態 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
實現什麼。
在 React 16.6 中,支持基礎組件的代碼分割和懶加載已經經過 React.lazy()
和 React.Suspense
添加。
React.lazy() 和 Suspense 尚未支持服務端。服務端的代碼分割,仍然使用 React-Loadable。
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>
}
複製代碼
一個使用 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())
)
})
複製代碼
下面的截屏展現了當渲染的時候組件看起來的示例。
若是你但願使用一個命名的導出組件,那麼你須要再次導出他們,做爲在獨立的中間模塊中的默認導出。
若是你有一個 OtherComponent
做爲命名導出模塊,你但願使用 React.lazy()
來加載 OtherComponent
,那麼你須要建立一箇中間模塊來再次導出 OtherComponent
做爲 默認模塊。
export const FirstComponent = () => {/* 組件邏輯 */}
export const SecondComponent = () => {/* 組件邏輯 */}
export const OtherComponent = () => {/* 組件邏輯 */}
複製代碼
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享受吧。