跳至主要内容

[ReactDoc] Code Splitting and Dynamic Imports

code-splitting @ React Docs

概念

code splitting 的意思是將一大包程式切分成許多不同的檔案(chunks),例如,把使用者不一定會用到的元件切成當一個檔案,使用者有要使用該元件是才去下載和使用該元件。

雖然透過 cde splitting 沒辦法減少 App 中整體程式碼的檔案大小,但可以避免使用者花時間和流量下載那些用不到的檔案,並能改善使用者第一次載人 App 時所需的時間。

使用

使用 Dynamic Import 來達到 code splitting

只需要使用 dynamic import 的方式,bundle 的工具就會自動幫忙完成 code splitting 的動作。dynamic import 只需要把 import 變成 Promise 即可:

//reactjs.org/docs/code-splitting.html#import
https: import('./math').then((math) => math.add(16, 26));

使用 lazy 來動態載入 react 元件

在 React 中,要使用 dynamic import 來載入 React 元件的話,可以搭配 React 提供的 lazySuspense

  • lazy 用來幫 react component 做 dynamic import
  • Suspensefallback 則是會在 dynamic import 時顯示的畫面
// STEP 1:載入 lazy 和 Suspense
import { lazy, Suspense } from 'react';
import Homepage from 'views/Homepage';

// STEP 2:使用 lazy 達到 dynamic import
const DemoContext = lazy(() => import('views/DemoContext'));

const App = () => {
return (
<div className="App">
{/* STEP 3: 加上 Suspense */}
<Suspense fallback={<h2>Dynamic Loading...</h2>}>
<Switch>
<Route path="/use-context">
<DemoContext />
</Route>

<Route path="/">
<Homepage />
</Route>
</Switch>
</Suspense>
</div>
);
};

由下圖可以看到,使用的 dynamic imports 後,使用者切換到該頁面時,才會實際去載入該元件對應的檔案(*.chunk.js):

code splitting

同樣的方式也可以用來載入和 UI 有關的元件(例如,Modal)。

warning

需要特別留意的是,並不是所有的元件或頁面都有使用 dynamic import 來達到 code splitting 的必要。因為使用 code splitting,也會需要額外的 glue code 來讓不同支檔案的程式碼可以順利的連接在一起,而這些程式碼也會增加檔案體積,因此對於原本檔案體積就不大的元件來說,這麼做可能反而會弄巧成拙。

Named Exports

React.lazy 目前只支援 default exports,如果你的元件是使用 named exports,需要多一個中介來把它轉成 default exports,如此才能確保 tree shaking 保持作用:

// https://reactjs.org/docs/code-splitting.html#named-exports

// intermediate module to transform named exports to default exports
export { MyComponent as default } from './ManyComponents.js';

Avoiding fallbacks

有些時候,我們希望沿用舊有已經出現的 UI,而不要在切換元件時再次進到 fallback,這時候可以使用 startTransition API,例如:

// https://reactjs.org/docs/code-splitting.html#avoiding-fallbacks

function MyComponent() {
const [tab, setTab] = React.useState('photos');

function handleTabSelect(tab) {
startTransition(() => {
setTab(tab);
});
}

return (
<div>
<Tabs onTabSelect={handleTabSelect} />
<Suspense fallback={<Glimmer />}>{tab === 'photos' ? <Photos /> : <Comments />}</Suspense>
</div>
);
}

如此,會告訴 React,這並不是一個急迫的更新,但使用者點擊切換頁籤時,React 不會進到 fallback,而是顯示原本的 UI,只到 <Comments /> 準備好後才切換過去。

Error boundaries

透過建立 Error boundaries 可以處理當網路有問題,無法動態載入該元件時要呈現的提示或畫面:

import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

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

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

function Error({ error }) {
return (
<div>
<h1>Application Error</h1>
<pre style={{ whiteSpace: 'pre-wrap' }}>{error.stack}</pre>
</div>
);
}