翻譯至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"));