React Code-Splitting

翻譯至React官方docs Code-Splitting

Bundling

越來(lái)越多的React應(yīng)用使用Webpack、Browserify等工具進(jìn)行資源打包。前端打包即是將所有的資源文件全部壓縮至一個(gè)文件里面:bundle。這個(gè)bundle被網(wǎng)頁(yè)加載后,整個(gè)web app便立馬被呈現(xiàn)出來(lái)。

Example

App:
// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}
Bundle:
function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

注意:
打包出來(lái)的bundle文件相對(duì)于之前會(huì)有大的變化

如果你使用Create React App, Next.js, Gatsby或者其他打包工具,你可以開(kāi)箱即用的Webpack來(lái)打包你的app。

如果你未使用以上工具,可以參考Webpack相關(guān)文檔開(kāi)始打包。Installation Getting Started

Code Splitting

應(yīng)用進(jìn)行bundle打包固然是好的,但隨著應(yīng)用的逐步增長(zhǎng),則打包的文件也會(huì)越來(lái)越龐大,特別是對(duì)于引用的三方庫(kù)。你打包時(shí)需要時(shí)刻注意這些三方庫(kù)代碼,以免在不經(jīng)意間使得打包文件過(guò)大而造成應(yīng)用加載的緩慢。

為了避免打包后的文件臃腫,我們推薦的解決方式是在代碼中使用”splitting”。Code-Splitting可以打出多個(gè)bundle,應(yīng)用可以根據(jù)此時(shí)的需要進(jìn)行實(shí)時(shí)bundle加載,目前Webpack和Browserify(通過(guò)factor-bundle方式)均已支持Code-Splitting。

Code-splitting通過(guò)按需加載的方式可以大幅提高應(yīng)用的體檢。 而且你不需要對(duì)你的代碼進(jìn)行刪減,你可以避免加載你并不需要的代碼,還可以減少初次加載的代碼。

import()

最好的方式使用code-splitting 是通過(guò)動(dòng)態(tài)import這種方式。

Before:
import { add } from './math';

console.log(add(16, 26));
After:
import("./math").then(math => {
  console.log(math.add(16, 26));
});

注意:
動(dòng)態(tài)import語(yǔ)法被列為ECMAScript下一代標(biāo)準(zhǔn)的建議中,預(yù)計(jì)在將來(lái)會(huì)被接收。

當(dāng)Webpack遇到這種語(yǔ)法時(shí),它會(huì)自動(dòng)在應(yīng)用中開(kāi)啟code-splitting。假如你使用的是Create React App創(chuàng)建的項(xiàng)目,它已經(jīng)在項(xiàng)目中自動(dòng)配置動(dòng)態(tài)import,你可以直接使用。它也可以在Next.js中開(kāi)箱即用。

假如你自己在項(xiàng)目中配置Webpack, name你需要盡可能地閱讀Webpack關(guān)于code splitting的指導(dǎo)。你的Webpack配置需要盡可能地與這個(gè)保持一致。

當(dāng)我們使用Babel工具時(shí),你需要清除知道Babel可以解析動(dòng)態(tài)import語(yǔ)法,但不會(huì)對(duì)其轉(zhuǎn)換。所以你需要 babel-plugin-syntax-dynamic-import這個(gè)插件。

React.lazy

注意
React.lazy 和 Suspense組件目前并沒(méi)有支持服務(wù)端渲染。如果你需要在服務(wù)端做code-splitting,我們還是推薦使用React Loadable。它對(duì)于服務(wù)端渲染做splitting有著良好的引導(dǎo)方式。

React.Lazy方法可以對(duì)常規(guī)組件進(jìn)行dynamic import。

Before:
import OtherComponent from './OtherComponent';

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}
After:
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

當(dāng)這個(gè)組件渲染完成后,它會(huì)自動(dòng)加載包含OtherComponent這個(gè)組件的bundle。
React.lazy必須使用dynamic import()方法。它會(huì)返回一個(gè)Promise,當(dāng)它resolve時(shí),它會(huì)返回一個(gè)默認(rèn)導(dǎo)出的React組件模塊。

Suspense

如果我們自定義的組件MyComponent渲染時(shí),這個(gè)包含OtherComponent的模塊并沒(méi)有加載,我們必須在加載過(guò)程中展示一些過(guò)渡內(nèi)容--比如加載進(jìn)度條。我們可以使用Suspense來(lái)完成這個(gè)事。

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

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

fallback prop 可以接受任意的React elements 當(dāng)你在等待其他組件渲染時(shí),你可以將Suspense放在任意的lazy component上。你甚至可以在一個(gè)Suspense component中包裹幾個(gè)lazy components

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

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Error boundaries

如果其他的模塊加載失敗(比如因?yàn)榫W(wǎng)絡(luò)異常),它會(huì)拋出一個(gè)錯(cuò)誤。你可以處理這些錯(cuò)誤,提供一個(gè)好的用戶體驗(yàn)并復(fù)原這些Error Boundary, 。一旦你創(chuàng)建了Error Boundary, 你可以在任意lazy components使用它。當(dāng)網(wǎng)絡(luò)錯(cuò)誤時(shí),可以顯示錯(cuò)誤的狀態(tài)。

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

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

Route-based code splitting

可能在應(yīng)用中開(kāi)始使用splitting code會(huì)有一點(diǎn)冒險(xiǎn).你需要確定當(dāng)你引入split bundles后不會(huì)影響到用戶體驗(yàn)。
先使用routes會(huì)是一個(gè)不錯(cuò)的方式。在開(kāi)發(fā)Web應(yīng)用時(shí),大多數(shù)人會(huì)因?yàn)榻缑娴霓D(zhuǎn)換而花費(fèi)大量的時(shí)間加載。你也同樣傾向于重新渲染整個(gè)界面一次,讓你們的用戶不能同時(shí)與界面上的其他元素交互。
下面是一個(gè)例子,展示了在基于route的方式下通過(guò)使用React Router with React.lazy這些庫(kù)進(jìn)行code splitting.

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Named Exports

React.lazy目前只支持 default exports默認(rèn)導(dǎo)出方式。如果你想要使用命名導(dǎo)出,你可以創(chuàng)建一個(gè)中間模塊進(jìn)行二次導(dǎo)出。它可以確保treeshaking(Webpack打包時(shí)移除不必要的組件)繼續(xù)工作,并且不會(huì)拉入未使用的組件。

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容