[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 提供的 lazy
和 Suspense
:
lazy
用來幫 react component 做 dynamic importSuspense
的fallback
則是會在 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
):
同樣的方式也可以用來載入和 UI 有關的元件(例如,Modal)。
需要特別留意的是,並不是所有的元件或頁面都有使用 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>
);
}