利用React v16.6 Lazy&Suspense提升應用效能
本篇文章示例程式碼
前言
利用懶載入(Lazy Loading)
優化頁面效能不是什麼新概念,不過React 16.6可以使用React.lazy
與Suspense
讓原生React實現Lazy Loading大大的簡化。
本篇文章也會擴充多種類似解決方案,見擴充
部分
主要關鍵詞說明
動態匯入(Dynamic Import)
動態匯入
目前只是TC39的一個提案,不是JS(ES)標準的一部分。該功能可以動態化載入我們分割的元件/頁面/檔案,具體提案見下方連結:
TC39 proposal-dynamic-import提案
目前要使用動態匯入
,需要使用到babel外掛:
babel-plugin-syntax-dynamic-import
React.lazy
lazy函式
提供了一種非常簡便的方法動態匯入元件,實現按需載入與程式碼分割
React.lazy使用方式非常簡單,示例:
// lazy 接受一個函式作為引數
const MyComponent = React.lazy(() => import(`path/MyComponent`));
// 展示時才載入(可以自定義一些條件)
const App = () => (
<div>
<MyComponent />
</div>
)
複製程式碼
Suspense
Suspense
元件用於包裝lazy元件,在lazy元件還沒有完全載入時,將fallback內容呈現給使用者。
用動態載入,編譯時會將檔案分割,從載入檔案到呈現會有時間延遲,此時可以使用Suspense
展示一個loading。
使用示例
import React, { Suspense } from `react`;
const MyComponent = React.lazy(() => import(`path/MyComponent`));
const Loading = (
<h3>loading...</h3>
);
const App = () => (
<div>
<Suspense fallback={Loading}>
<MyComponent />
</Suspense>
</div>
)
複製程式碼
ErrorBoundary
ErrorBoundary(錯誤邊界)
,可以使用ErrorBoundary包裝Suspense,當Suspense出錯時,統一處理錯誤。當然ErrorBoundary
不止可用於Suspense,可以包裝任意元件,然後統一處理錯誤。
實際場景中,可以自己先寫一些通用錯誤頁/元件繼承自ErrorBoundary
,然後包裝要處理的元件。
也可以在元件的componentDidCatch
生命週期中單獨處理錯誤。
應用
效能優化說明
針對React載入的優化,可以分為兩類:
- 頁面載入優化
- 元件載入優化
很明顯,React.lazy + Suspense可以很方便的應用於元件載入優化場景。
示例場景
CSR(客戶端渲染)應用
多頁面的中某個耗時元件的載入
路由層
import React from `react`;
import { BrowserRouter as Router, Route } from `react-router-dom`;
import Header from `./header`;
import Index from `../pages`;
import Welcome from `../pages/welcome`;
export default () => (
<Router>
<div>
<Header />
<Route exact path="/" component={Index}/>
<Route path="/welcome" component={Welcome}/>
</div>
</Router>
);
複製程式碼
某個存在耗時元件的頁面
// welcome.js
import React, { Suspense } from `react`;
// 使用Suspense + React.lazy動態載入的耗時元件
const Content = React.lazy(() => import(`../components/content`));
export default () => (
<div>
<h1>Welcome</h1>
<Suspense fallback={<div>loading...</div>}>
<Content/>
</Suspense>
</div>
);
複製程式碼
說明
假設用webpack編譯上面程式碼產出bundle.js。當進入首頁時,載入的bundle.js不會包含Welcome Content部分的程式碼,切換頁面到Welcome時會走網路載入需要的檔案。
利用這種方法拆分多個元件時,可以顯著的提升首屏時間。
完整示例專案
說明:該專案主要用於React一些新功能及有意思的用法示例,會持續更新,後面會涉及到hooks
及最新原始碼相關,有興趣的可以star。
擴充
針對SSR(服務端渲染)應用匯入效能優化
可以使用 Next.js ,Next.js已支援直接使用dynamic import,並且可以指明什麼時候動態匯入(服務端/客戶端),Next.js對首屏做了很多優化,首屏時,服務端會尋找到最簡依賴渲染,具體可以點選前面連結檢視文件。示例:
// Next.js 動態匯入
const MyComponent = dynamic(import(`../components/MyComponent`), {
ssr: false,
loading: () => false,
});
複製程式碼
針對元件的動態載入
針對圖片的lazy loading
針對動態請求
- react-request:對於CSR應用,在不用hooks的情況下,也可以不在componentDidMount中處理請求,讓請求更加的優雅。
- react-async:利用
Render Props + React hooks + Context API
統一處理請求,支援SSR使用
封裝react-request
示例(統一請求處理)
import React from `react`;
import PropTypes from `prop-types`;
import { Fetch } from `react-request`;
// import ReactLoading from `react-loading`;
import config from `../../config`;
const CustomFetch = (props) => {
const { url, children } = props;
return (
<Fetch
{...props}
url={config.serverUrl + url}
transformData={data => data.data}
>
{({ fetching, failed, data }) => {
if (fetching) {
// 正在請求:可以展示loading動畫
// return <ReactLoading type="cylon" color="#2db53f" height="10%" width="10%" />;
}
if (failed) {
// 當前元件請求失敗
// return <h1 className="home">請求失敗...</h1>;
}
if (data) {
// 請求成功
return children(data);
}
return null;
}}
</Fetch>
);
};
CustomFetch.propTypes = {
url: PropTypes.string.isRequired,
};
export default CustomFetch;
複製程式碼
在元件中使用CustomFetch
buildLinks = () => (
<div className="links">
<CustomFetch
url="/getLinks"
>
{data => data.map((item, index) => (
<a className="item" href={item.url} key={`link-${index}`}>
<img src={item.icon} className="icon" alt={item.name} />
<p>{item.name}</p>
</a>
))}
</CustomFetch>
</div>
);
複製程式碼